From 783cf1715f445ad4c0bd4376adfd57a5e8447199 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 15 Sep 2026 17:25:21 -0700 Subject: [PATCH 01/43] chore(db): drop retired usage columns and compatibility scaffolding (#7868) * chore(db): drop retired usage columns and compatibility scaffolding * fix(db): forward force flags for local and dev schema pushes --- .github/CONTRIBUTING.md | 2 +- .github/workflows/migrations.yml | 3 +- .github/workflows/test-build.yml | 6 + apps/sim/app/api/files/uploads/finalizers.ts | 9 +- apps/sim/app/api/organizations/[id]/route.ts | 8 +- apps/sim/app/api/v1/admin/credits/route.ts | 5 +- .../admin/organizations/[id]/billing/route.ts | 4 +- .../api/v1/admin/organizations/[id]/route.ts | 8 +- .../app/api/v1/admin/organizations/route.ts | 4 +- .../api/v1/admin/users/[id]/billing/route.ts | 20 +- .../workspace-forking/lib/copy/copy-files.ts | 7 +- apps/sim/lib/admin/dashboard.ts | 15 +- apps/sim/lib/auth/anonymous.ts | 3 +- .../auth/sim-auth-adapter.postgres.test.ts | 7 +- .../sim/lib/auth/sim-auth-adapter.sql.test.ts | 11 - apps/sim/lib/auth/sim-auth-adapter.ts | 9 +- apps/sim/lib/billing/core/organization.ts | 13 +- apps/sim/lib/billing/core/usage.ts | 13 +- .../lib/billing/enterprise-provisioning.ts | 4 +- apps/sim/lib/billing/organization.ts | 11 +- .../organizations/create-organization.ts | 5 +- .../lib/billing/organizations/membership.ts | 4 +- apps/sim/lib/billing/storage/tracking.ts | 5 +- apps/sim/lib/billing/threshold-billing.ts | 15 +- apps/sim/lib/copilot/chat/fork-chat-files.ts | 7 +- .../tools/handlers/upload-file-reader.ts | 8 +- ...rganization-personal-tokens.integration.ts | 4 +- .../lib/data-drains/sources/workflow-logs.ts | 4 +- ...xecution-archive-provenance.integration.ts | 4 +- .../external-file-provenance.integration.ts | 14 +- .../organization-mcp-search.integration.ts | 4 +- .../seed-source-access-fixture.ts | 4 +- .../slack-search-turns.integration.ts | 4 +- .../upload-read-provenance.integration.ts | 6 +- apps/sim/lib/logs/execution/logger.ts | 16 +- apps/sim/lib/public-shares/share-manager.ts | 11 +- .../application.integration.ts | 5 +- .../workspace/workspace-file-manager.ts | 31 +- apps/sim/lib/uploads/server/metadata.ts | 31 +- packages/db/insert-columns.test.ts | 145 - packages/db/insert-columns.ts | 35 - .../0348_drop_retired_usage_columns.sql | 82 + .../db/migrations/meta/0348_snapshot.json | 26694 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/package.json | 7 +- packages/db/schema.ts | 119 +- ...rations-paused-billing-attribution.test.ts | 2 - .../0003_backfill_workspace_storage_usage.ts | 5 - ...backfill_workspace_file_size_bytes.test.ts | 59 - ...0008_backfill_workspace_file_size_bytes.ts | 87 - ...9_backfill_wel_residual_cost_total.test.ts | 50 - .../0009_backfill_wel_residual_cost_total.ts | 99 - ...credential_group_resource_policies.test.ts | 6 +- packages/db/script-migrations/index.ts | 4 - .../apply-dev-workspace-file-size-cutover.ts | 46 - packages/db/scripts/push.ts | 13 + .../scripts/retired-columns.postgres.test.ts | 131 + packages/db/workspace-files-schema.test.ts | 9 - packages/testing/src/mocks/schema.mock.ts | 47 - scripts/check-pending-drop-tables.test.ts | 185 +- scripts/check-pending-drop-tables.ts | 164 +- 61 files changed, 27114 insertions(+), 1226 deletions(-) delete mode 100644 packages/db/insert-columns.test.ts delete mode 100644 packages/db/insert-columns.ts create mode 100644 packages/db/migrations/0348_drop_retired_usage_columns.sql create mode 100644 packages/db/migrations/meta/0348_snapshot.json delete mode 100644 packages/db/script-migrations/0008_backfill_workspace_file_size_bytes.test.ts delete mode 100644 packages/db/script-migrations/0008_backfill_workspace_file_size_bytes.ts delete mode 100644 packages/db/script-migrations/0009_backfill_wel_residual_cost_total.test.ts delete mode 100644 packages/db/script-migrations/0009_backfill_wel_residual_cost_total.ts delete mode 100644 packages/db/scripts/apply-dev-workspace-file-size-cutover.ts create mode 100644 packages/db/scripts/push.ts create mode 100644 packages/db/scripts/retired-columns.postgres.test.ts delete mode 100644 packages/db/workspace-files-schema.test.ts diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index bc4666f7875..8dac3590f20 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -254,7 +254,7 @@ If you prefer not to use Docker. **All commands run from the repository root unl cd packages/db && bun run db:migrate && cd ../.. ``` - For ad-hoc schema iteration during development you can also use `bun run db:push` from `packages/db`, but `db:migrate` is the canonical command for both local and CI/CD setups. + For ad-hoc schema iteration during development you can also use `bun run db:push` from `packages/db`, but `db:migrate` is the canonical command for staging and production. `db:push` reconciles directly to the current schema without running versioned migration guards or backfills. For disposable local/dev databases, `bun run db:push --force` accepts Drizzle's data-loss prompts, including column drops. 4. **Run the Development Servers:** diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index faa2b464522..5b06c42bc14 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -71,6 +71,8 @@ jobs: if [ "${ENVIRONMENT}" = "dev" ]; then echo "Dev environment — pushing schema directly (db:push)" + # Dev deliberately forces direct schema reconciliation; staging and + # production use guarded versioned migrations in the other branch. # drizzle-kit push needs a TTY to resolve ambiguous renames (--force only # covers data-loss). In CI it throws "Interactive prompts require a TTY # terminal" but still exits 0, so the job goes green without applying the @@ -81,7 +83,6 @@ jobs: echo "ERROR: db:push needs an interactive rename decision; land it as a versioned migration instead of relying on push." >&2 exit 1 fi - bun run ./scripts/apply-dev-workspace-file-size-cutover.ts else echo "Applying versioned migrations (db:migrate)" bun run ./scripts/migrate.ts diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index d7122f1e889..9116ef7d6bb 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -85,6 +85,12 @@ jobs: working-directory: packages/db run: bun run db:migrate + - name: Verify retired-column contract migration in PostgreSQL + working-directory: packages/db + env: + RETIRED_COLUMNS_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim + run: bunx vitest run scripts/retired-columns.postgres.test.ts + - name: Verify OAuth lifecycle and SCIM membership guards in PostgreSQL working-directory: apps/sim # These suites share a schema and install triggers; parallel files can deadlock DDL against cleanup. diff --git a/apps/sim/app/api/files/uploads/finalizers.ts b/apps/sim/app/api/files/uploads/finalizers.ts index 963b421720c..4007f4802a5 100644 --- a/apps/sim/app/api/files/uploads/finalizers.ts +++ b/apps/sim/app/api/files/uploads/finalizers.ts @@ -1,8 +1,7 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { type Principal, resolvePrincipalAuditAttribution } from '@sim/auth/principal' import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' -import { type WorkspaceFileRow, workspaceFileColumns, workspaceFiles } from '@sim/db/schema' +import { type WorkspaceFileRow, workspaceFiles } from '@sim/db/schema' import { generateId } from '@sim/utils/id' import { eq, sql } from 'drizzle-orm' import type { V2File } from '@/lib/api/contracts/v2/files' @@ -367,7 +366,7 @@ async function insertOrLoadFileMetadata( const now = new Date() const [inserted] = await db - .insert(withInsertColumns(workspaceFiles, workspaceFileColumns)) + .insert(workspaceFiles) .values({ id: generateId(), key: input.key, @@ -384,7 +383,7 @@ async function insertOrLoadFileMetadata( contentUpdatedAt: now, }) .onConflictDoNothing() - .returning(workspaceFileColumns) + .returning() if (inserted) return { file: inserted, created: true } @@ -399,7 +398,7 @@ async function insertOrLoadFileMetadata( async function findFileMetadataByKey(key: string): Promise { const [file] = await db - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where(eq(workspaceFiles.key, key)) .orderBy(sql`${workspaceFiles.deletedAt} IS NULL DESC`) diff --git a/apps/sim/app/api/organizations/[id]/route.ts b/apps/sim/app/api/organizations/[id]/route.ts index a84bf866f19..ba074c3ab5f 100644 --- a/apps/sim/app/api/organizations/[id]/route.ts +++ b/apps/sim/app/api/organizations/[id]/route.ts @@ -1,6 +1,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { db } from '@sim/db' -import { member, organization, organizationColumns } from '@sim/db/schema' +import { member, organization } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { isOrgAdminRole } from '@sim/platform-authz/workspace' import { and, eq, ne } from 'drizzle-orm' @@ -64,7 +64,7 @@ export const GET = withRouteHandler( } const organizationEntry = await db - .select(organizationColumns) + .select() .from(organization) .where(eq(organization.id, organizationId)) .limit(1) @@ -156,7 +156,7 @@ export const PUT = withRouteHandler( if (name !== undefined || slug !== undefined || logo !== undefined) { if (slug !== undefined) { const existingSlug = await db - .select(organizationColumns) + .select() .from(organization) .where(and(eq(organization.slug, slug), ne(organization.id, organizationId))) .limit(1) @@ -180,7 +180,7 @@ export const PUT = withRouteHandler( .update(organization) .set(updateData) .where(eq(organization.id, organizationId)) - .returning(organizationColumns) + .returning() if (updatedOrg.length === 0) { return NextResponse.json({ error: 'Organization not found' }, { status: 404 }) diff --git a/apps/sim/app/api/v1/admin/credits/route.ts b/apps/sim/app/api/v1/admin/credits/route.ts index da245cdc7a4..2c41426d1f2 100644 --- a/apps/sim/app/api/v1/admin/credits/route.ts +++ b/apps/sim/app/api/v1/admin/credits/route.ts @@ -25,8 +25,7 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' -import { organization, subscription, user, userStats, userStatsColumns } from '@sim/db/schema' +import { organization, subscription, user, userStats } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateShortId } from '@sim/utils/id' import { normalizeEmail } from '@sim/utils/string' @@ -156,7 +155,7 @@ export const POST = withRouteHandler( .limit(1) if (!existingStats) { - await db.insert(withInsertColumns(userStats, userStatsColumns)).values({ + await db.insert(userStats).values({ id: generateShortId(), userId: entityId, }) diff --git a/apps/sim/app/api/v1/admin/organizations/[id]/billing/route.ts b/apps/sim/app/api/v1/admin/organizations/[id]/billing/route.ts index 40263d252c4..6fca93b98d1 100644 --- a/apps/sim/app/api/v1/admin/organizations/[id]/billing/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/[id]/billing/route.ts @@ -16,7 +16,7 @@ */ import { db, dbReplica } from '@sim/db' -import { member, organization, organizationColumns } from '@sim/db/schema' +import { member, organization } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { count, eq } from 'drizzle-orm' import { @@ -155,7 +155,7 @@ export const PATCH = withRouteHandler( if (!parsed.success) return parsed.response const [orgData] = await db - .select(organizationColumns) + .select() .from(organization) .where(eq(organization.id, organizationId)) .limit(1) diff --git a/apps/sim/app/api/v1/admin/organizations/[id]/route.ts b/apps/sim/app/api/v1/admin/organizations/[id]/route.ts index 15ca0aca873..d33652870ae 100644 --- a/apps/sim/app/api/v1/admin/organizations/[id]/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/[id]/route.ts @@ -38,7 +38,7 @@ import { recordAuditBatch, } from '@sim/audit' import { db } from '@sim/db' -import { member, organization, organizationColumns, subscription } from '@sim/db/schema' +import { member, organization, subscription } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, count, eq, inArray, isNull, not, or } from 'drizzle-orm' import { @@ -93,7 +93,7 @@ export const GET = withRouteHandler( try { const [orgData] = await db - .select(organizationColumns) + .select() .from(organization) .where(eq(organization.id, organizationId)) .limit(1) @@ -144,7 +144,7 @@ export const PATCH = withRouteHandler( try { const [existing] = await db - .select(organizationColumns) + .select() .from(organization) .where(eq(organization.id, organizationId)) .limit(1) @@ -183,7 +183,7 @@ export const PATCH = withRouteHandler( .update(organization) .set(updateData) .where(eq(organization.id, organizationId)) - .returning(organizationColumns) + .returning() const updatedFields = auditUpdatedFields(updateData) logger.info(`Admin API: Updated organization ${organizationId}`, { updatedFields }) diff --git a/apps/sim/app/api/v1/admin/organizations/route.ts b/apps/sim/app/api/v1/admin/organizations/route.ts index 497d0cad173..bc95ea0823d 100644 --- a/apps/sim/app/api/v1/admin/organizations/route.ts +++ b/apps/sim/app/api/v1/admin/organizations/route.ts @@ -27,7 +27,7 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { db, dbReplica } from '@sim/db' -import { member, organization, organizationColumns, user } from '@sim/db/schema' +import { member, organization, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { slugify } from '@sim/utils/string' import { count, eq } from 'drizzle-orm' @@ -155,7 +155,7 @@ export const POST = withRouteHandler( }) const [createdOrg] = await db - .select(organizationColumns) + .select() .from(organization) .where(eq(organization.id, organizationId)) .limit(1) diff --git a/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts b/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts index 4ab52cb09e9..6907ddcc0c0 100644 --- a/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts +++ b/apps/sim/app/api/v1/admin/users/[id]/billing/route.ts @@ -20,15 +20,7 @@ */ import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' -import { - member, - organization, - subscription, - user, - userStats, - userStatsColumns, -} from '@sim/db/schema' +import { member, organization, subscription, user, userStats } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateShortId } from '@sim/utils/id' import { eq, or } from 'drizzle-orm' @@ -86,11 +78,7 @@ export const GET = withRouteHandler( return notFoundResponse('User') } - const [stats] = await db - .select(userStatsColumns) - .from(userStats) - .where(eq(userStats.userId, userId)) - .limit(1) + const [stats] = await db.select().from(userStats).where(eq(userStats.userId, userId)).limit(1) // Canonical current-period usage (attributed usage_log, refresh-adjusted) // comes from the same helper users see. @@ -180,7 +168,7 @@ export const PATCH = withRouteHandler( } const [existingStats] = await db - .select(userStatsColumns) + .select() .from(userStats) .where(eq(userStats.userId, userId)) .limit(1) @@ -248,7 +236,7 @@ export const PATCH = withRouteHandler( if (existingStats) { await db.update(userStats).set(updateData).where(eq(userStats.userId, userId)) } else { - await db.insert(withInsertColumns(userStats, userStatsColumns)).values({ + await db.insert(userStats).values({ id: generateShortId(), userId, ...updateData, diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts index b2fc0597106..1610f6828df 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts @@ -1,6 +1,5 @@ import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' -import { workspaceFileColumns, workspaceFiles } from '@sim/db/schema' +import { workspaceFiles } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' @@ -197,7 +196,7 @@ export async function planForkFileCopies(params: { selectors.length === 0 ? [] : await tx - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( and( @@ -363,7 +362,7 @@ export async function executeForkFileBlobCopies( await db.transaction(async (tx) => { assertForkCopyActive(control) const [inserted] = await tx - .insert(withInsertColumns(workspaceFiles, workspaceFileColumns)) + .insert(workspaceFiles) .values({ id: task.targetFileId, key: task.targetKey, diff --git a/apps/sim/lib/admin/dashboard.ts b/apps/sim/lib/admin/dashboard.ts index b66d94f6ae1..bdb62e65b72 100644 --- a/apps/sim/lib/admin/dashboard.ts +++ b/apps/sim/lib/admin/dashboard.ts @@ -1,10 +1,8 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import { member, organization, - organizationColumns, organizationMemberUsageLimit, outboxEvent, permissions, @@ -12,7 +10,6 @@ import { usageLog, user, userStats, - userStatsColumns, workspace, } from '@sim/db/schema' import { generateId } from '@sim/utils/id' @@ -654,11 +651,7 @@ export async function listDashboardUsers({ search, limit, offset }: PaginationIn async function getDashboardOrganizationSummary(organizationId: string) { const [[org], [memberCountRow], [externalCountRow], latestSubscription, provisionings] = await Promise.all([ - db - .select(organizationColumns) - .from(organization) - .where(eq(organization.id, organizationId)) - .limit(1), + db.select().from(organization).where(eq(organization.id, organizationId)).limit(1), db.select({ value: count() }).from(member).where(eq(member.organizationId, organizationId)), db .select({ value: countDistinct(permissions.userId) }) @@ -1284,7 +1277,7 @@ export async function updateDashboardOrganizationLimits( const providerBacked = await db.transaction(async (tx) => { await acquireOrganizationMutationLock(tx, organizationId) const [org] = await tx - .select(organizationColumns) + .select() .from(organization) .where(eq(organization.id, organizationId)) .for('update') @@ -1418,7 +1411,7 @@ export async function grantDashboardOrganizationBalance( }), operation: async () => { const [org] = await tx - .select(organizationColumns) + .select() .from(organization) .where(eq(organization.id, organizationId)) .for('update') @@ -1526,7 +1519,7 @@ export async function grantDashboardUserBalance( ? null : getPerUserMinimumLimit(initialSubscription).toString() await tx - .insert(withInsertColumns(userStats, userStatsColumns)) + .insert(userStats) .values({ id: generateId(), userId, diff --git a/apps/sim/lib/auth/anonymous.ts b/apps/sim/lib/auth/anonymous.ts index 465992f6ddd..c4be061bea0 100644 --- a/apps/sim/lib/auth/anonymous.ts +++ b/apps/sim/lib/auth/anonymous.ts @@ -1,5 +1,4 @@ import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import * as schema from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId } from '@sim/utils/id' @@ -38,7 +37,7 @@ export async function ensureAnonymousUserExists(): Promise { }) if (!existingStats) { - await db.insert(withInsertColumns(schema.userStats, schema.userStatsColumns)).values({ + await db.insert(schema.userStats).values({ id: generateId(), userId: ANONYMOUS_USER_ID, currentUsageLimit: '10000000000', diff --git a/apps/sim/lib/auth/sim-auth-adapter.postgres.test.ts b/apps/sim/lib/auth/sim-auth-adapter.postgres.test.ts index e0c71ca48d9..07389be1950 100644 --- a/apps/sim/lib/auth/sim-auth-adapter.postgres.test.ts +++ b/apps/sim/lib/auth/sim-auth-adapter.postgres.test.ts @@ -5,7 +5,6 @@ import * as schema from '@sim/db/schema' import { withUtcTimestamps } from '@sim/db/timestamps' import { generateId } from '@sim/utils/id' import type { BetterAuthOptions } from 'better-auth' -import { drizzleAdapter } from 'better-auth/adapters/drizzle' import { organization } from 'better-auth/plugins' import { drizzle } from 'drizzle-orm/postgres-js' import postgres from 'postgres' @@ -62,14 +61,10 @@ describe.skipIf(!databaseUrl)('Better Auth across the organization column drop', ? adapter.transaction((tx) => exerciseOrganization(tx)) : exerciseOrganization(adapter) + await client`ALTER TABLE pg_temp.organization ADD COLUMN departed_member_usage numeric NOT NULL DEFAULT 0` await exercise() await client`ALTER TABLE pg_temp.organization DROP COLUMN departed_member_usage` - const unprojected = drizzleAdapter(database, { provider: 'pg', schema })(OPTIONS) - await expect( - unprojected.findOne({ model: 'organization', where: [{ field: 'id', value: 'missing' }] }) - ).rejects.toMatchObject({ cause: { code: '42703' } }) - await exercise() } finally { await client.end() diff --git a/apps/sim/lib/auth/sim-auth-adapter.sql.test.ts b/apps/sim/lib/auth/sim-auth-adapter.sql.test.ts index a4e71fc7337..fbc6610331d 100644 --- a/apps/sim/lib/auth/sim-auth-adapter.sql.test.ts +++ b/apps/sim/lib/auth/sim-auth-adapter.sql.test.ts @@ -1,9 +1,7 @@ /** * @vitest-environment node */ -import * as schema from '@sim/db/schema' import type { BetterAuthOptions } from 'better-auth' -import { drizzleAdapter } from 'better-auth/adapters/drizzle' import { organization } from 'better-auth/plugins' import { drizzle } from 'drizzle-orm/pg-proxy' import { describe, expect, it, vi } from 'vitest' @@ -70,13 +68,4 @@ describe('Better Auth organization SQL', () => { expect(query, operation.name).not.toContain('"departed_member_usage"') } }) - - it('retains the full migration schema while the unprojected adapter remains incompatible', async () => { - const execute = vi.fn(async (_query: string) => ({ rows: [] })) - const adapter = drizzleAdapter(drizzle(execute), { provider: 'pg', schema })(OPTIONS) - - await adapter.findOne({ model: 'organization', where: WHERE }) - - expect(execute.mock.calls[0][0]).toContain('"departed_member_usage"') - }) }) diff --git a/apps/sim/lib/auth/sim-auth-adapter.ts b/apps/sim/lib/auth/sim-auth-adapter.ts index 6ea6be9d05c..b98fe240bbf 100644 --- a/apps/sim/lib/auth/sim-auth-adapter.ts +++ b/apps/sim/lib/auth/sim-auth-adapter.ts @@ -1,5 +1,4 @@ import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import * as schema from '@sim/db/schema' import type { BetterAuthOptions } from 'better-auth' import { drizzleAdapter } from 'better-auth/adapters/drizzle' @@ -12,12 +11,6 @@ import { guardSubscriptionPlanWrites } from '@/lib/auth/stripe-adapter-guard' type BetterAuthAdapter = ReturnType> -/** Better Auth's implicit reads, INSERT defaults, and RETURNING must use live columns. */ -const AUTH_SCHEMA = { - ...schema, - organization: withInsertColumns(schema.organization, schema.organizationColumns), -} - /** * Builds every Better Auth adapter surface, including transactional callbacks, * with Sim's write invariants applied to the actual Drizzle connection in use. @@ -29,7 +22,7 @@ export function createSimAuthAdapter( ): BetterAuthAdapter { const base = drizzleAdapter(database, { provider: 'pg', - schema: AUTH_SCHEMA, + schema, transaction: false, })(options) const guarded = guardSubscriptionPlanWrites(guardOAuthProviderWrites(base, database)) diff --git a/apps/sim/lib/billing/core/organization.ts b/apps/sim/lib/billing/core/organization.ts index 6281dfa4bb6..d33afcd4753 100644 --- a/apps/sim/lib/billing/core/organization.ts +++ b/apps/sim/lib/billing/core/organization.ts @@ -1,12 +1,5 @@ import { db } from '@sim/db' -import { - member, - organization, - organizationColumns, - usageLog, - user, - userStats, -} from '@sim/db/schema' +import { member, organization, usageLog, user, userStats } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, count, eq, gte, lt, sql } from 'drizzle-orm' import { isOrganizationBillingBlocked } from '@/lib/billing/core/access' @@ -196,7 +189,7 @@ export async function getOrganizationBillingData( try { // Get organization info const orgRecord = await executor - .select(organizationColumns) + .select() .from(organization) .where(eq(organization.id, organizationId)) .limit(1) @@ -375,7 +368,7 @@ export async function updateOrganizationUsageLimit( try { // Validate the organization exists const orgRecord = await db - .select(organizationColumns) + .select() .from(organization) .where(eq(organization.id, organizationId)) .limit(1) diff --git a/apps/sim/lib/billing/core/usage.ts b/apps/sim/lib/billing/core/usage.ts index 1a9ad247645..6231b7d81d2 100644 --- a/apps/sim/lib/billing/core/usage.ts +++ b/apps/sim/lib/billing/core/usage.ts @@ -1,6 +1,5 @@ import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' -import { member, organization, settings, user, userStats, userStatsColumns } from '@sim/db/schema' +import { member, organization, settings, user, userStats } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { isOrgAdminRole } from '@sim/platform-authz/workspace' import { generateId } from '@sim/utils/id' @@ -156,7 +155,7 @@ export async function getOrgUsageLimit( */ export async function handleNewUser(userId: string): Promise { try { - await db.insert(withInsertColumns(userStats, userStatsColumns)).values({ + await db.insert(userStats).values({ id: generateId(), userId: userId, currentUsageLimit: getFreeTierLimit().toString(), @@ -183,7 +182,7 @@ export async function handleNewUser(userId: string): Promise { */ export async function ensureUserStatsExists(userId: string): Promise { await db - .insert(withInsertColumns(userStats, userStatsColumns)) + .insert(userStats) .values({ id: generateId(), userId: userId, @@ -214,7 +213,7 @@ export async function getResolvedUserUsageData( // inserted, which a lagging replica can miss (this path throws on a // missing row). Stays on the primary deliberately. db - .select(userStatsColumns) + .select() .from(userStats) .where(eq(userStats.userId, userId)) .limit(1), @@ -331,7 +330,7 @@ export async function getUserUsageLimitInfo(userId: string): Promise { const [subscription, currentUserStats] = await Promise.all([ getHighestPriorityPersonalSubscription(userId, { onError: 'throw' }), - db.select(userStatsColumns).from(userStats).where(eq(userStats.userId, userId)).limit(1), + db.select().from(userStats).where(eq(userStats.userId, userId)).limit(1), ]) if (currentUserStats.length === 0) { diff --git a/apps/sim/lib/billing/enterprise-provisioning.ts b/apps/sim/lib/billing/enterprise-provisioning.ts index 50918ed50d6..6a5f1152037 100644 --- a/apps/sim/lib/billing/enterprise-provisioning.ts +++ b/apps/sim/lib/billing/enterprise-provisioning.ts @@ -1,12 +1,10 @@ import { AuditAction, AuditResourceType, recordAudit, recordAuditOnce } from '@sim/audit' import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import { invitation, invitationWorkspaceGrant, member, organization, - organizationColumns, outboxEvent, permissions, subscription, @@ -1640,7 +1638,7 @@ export async function issueEnterpriseProvisioning( if (organizationToCreate) { const now = new Date() - await tx.insert(withInsertColumns(organization, organizationColumns)).values({ + await tx.insert(organization).values({ id: organizationToCreate.id, name: organizationToCreate.name, slug: slugifyOrganizationName(organizationToCreate.name, organizationToCreate.id), diff --git a/apps/sim/lib/billing/organization.ts b/apps/sim/lib/billing/organization.ts index e1e3e3d2951..fc6219b250c 100644 --- a/apps/sim/lib/billing/organization.ts +++ b/apps/sim/lib/billing/organization.ts @@ -1,12 +1,5 @@ import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' -import { - member, - organization, - organizationColumns, - subscription as subscriptionTable, - user, -} from '@sim/db/schema' +import { member, organization, subscription as subscriptionTable, user } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { isOrgAdminRole } from '@sim/platform-authz/workspace' import { generateId } from '@sim/utils/id' @@ -449,7 +442,7 @@ export async function ensureOrganizationForTeamSubscriptionTx( organizationId = `org_${generateId()}` const now = new Date() - await tx.insert(withInsertColumns(organization, organizationColumns)).values({ + await tx.insert(organization).values({ id: organizationId, name: userData.name || `${userData.email || 'User'}'s Team`, slug: `${userId}-team-${generateId()}` diff --git a/apps/sim/lib/billing/organizations/create-organization.ts b/apps/sim/lib/billing/organizations/create-organization.ts index 9fb27bffb74..1947a333745 100644 --- a/apps/sim/lib/billing/organizations/create-organization.ts +++ b/apps/sim/lib/billing/organizations/create-organization.ts @@ -1,6 +1,5 @@ import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' -import { member, organization, organizationColumns } from '@sim/db/schema' +import { member, organization } from '@sim/db/schema' import { generateId } from '@sim/utils/id' import { and, eq, ne } from 'drizzle-orm' import { acquireUserBillingIdentityLock } from '@/lib/billing/organizations/billing-identity-lock' @@ -101,7 +100,7 @@ export async function createOrganizationWithOwnerTx( throw new OrganizationSlugTakenError(slug) } - await tx.insert(withInsertColumns(organization, organizationColumns)).values({ + await tx.insert(organization).values({ id: organizationId, name, slug, diff --git a/apps/sim/lib/billing/organizations/membership.ts b/apps/sim/lib/billing/organizations/membership.ts index 7687d32db23..d4bd323b3cb 100644 --- a/apps/sim/lib/billing/organizations/membership.ts +++ b/apps/sim/lib/billing/organizations/membership.ts @@ -6,7 +6,6 @@ */ import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import { account, credential, @@ -19,7 +18,6 @@ import { subscription as subscriptionTable, user, userStats, - userStatsColumns, workspace, workspaceFiles, } from '@sim/db/schema' @@ -1839,7 +1837,7 @@ export async function transferOrganizationOwnership( if (oldStats) { await tx - .insert(withInsertColumns(userStats, userStatsColumns)) + .insert(userStats) .values({ id: generateId(), userId: newOwnerUserId, diff --git a/apps/sim/lib/billing/storage/tracking.ts b/apps/sim/lib/billing/storage/tracking.ts index 7f416fe0180..7ae702a94c3 100644 --- a/apps/sim/lib/billing/storage/tracking.ts +++ b/apps/sim/lib/billing/storage/tracking.ts @@ -17,8 +17,7 @@ * writes any of them or deletes a locked row. */ -import { withInsertColumns } from '@sim/db/insert-columns' -import { organization, userStats, userStatsColumns, workspace } from '@sim/db/schema' +import { organization, userStats, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId } from '@sim/utils/id' import { isRecordLike } from '@sim/utils/object' @@ -632,7 +631,7 @@ export async function checkAndIncrementStorageUsageInTx( if (!orgScoped) { await tx - .insert(withInsertColumns(userStats, userStatsColumns)) + .insert(userStats) .values({ id: generateId(), userId, diff --git a/apps/sim/lib/billing/threshold-billing.ts b/apps/sim/lib/billing/threshold-billing.ts index 6ac67ebce27..3758e692805 100644 --- a/apps/sim/lib/billing/threshold-billing.ts +++ b/apps/sim/lib/billing/threshold-billing.ts @@ -1,13 +1,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { db } from '@sim/db' -import { - member, - organization, - organizationColumns, - subscription, - userStats, - userStatsColumns, -} from '@sim/db/schema' +import { member, organization, subscription, userStats } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { and, eq, sql } from 'drizzle-orm' @@ -355,7 +348,7 @@ export async function checkAndBillOverageThreshold( await tx.execute(sql.raw(`SET LOCAL lock_timeout = '${BILLING_LOCK_TIMEOUT_MS}ms'`)) const statsRecords = await tx - .select(userStatsColumns) + .select() .from(userStats) .where(eq(userStats.userId, userId)) .for('update') @@ -710,7 +703,7 @@ async function checkAndBillOrganizationOverageThreshold( } const ownerStatsLock = await tx - .select(userStatsColumns) + .select() .from(userStats) .where(eq(userStats.userId, lockedOwnerId)) .for('update') @@ -737,7 +730,7 @@ async function checkAndBillOrganizationOverageThreshold( } const orgLock = await tx - .select(organizationColumns) + .select() .from(organization) .where(eq(organization.id, organizationId)) .for('update') diff --git a/apps/sim/lib/copilot/chat/fork-chat-files.ts b/apps/sim/lib/copilot/chat/fork-chat-files.ts index 084bc682cf5..e06f83ae9f5 100644 --- a/apps/sim/lib/copilot/chat/fork-chat-files.ts +++ b/apps/sim/lib/copilot/chat/fork-chat-files.ts @@ -1,5 +1,4 @@ -import { withInsertColumns } from '@sim/db/insert-columns' -import { type WorkspaceFileRow, workspaceFileColumns, workspaceFiles } from '@sim/db/schema' +import { type WorkspaceFileRow, workspaceFiles } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { generateShortId } from '@sim/utils/id' @@ -62,7 +61,7 @@ export async function listForkableChatFiles( chatId: string ): Promise { return db - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( and( @@ -146,7 +145,7 @@ export async function planChatFileCopies(params: { // Ids and keys are generated client-side, so one multi-row insert suffices — // no per-row round trips while the fork transaction is held open. if (copyRows.length > 0) { - await tx.insert(withInsertColumns(workspaceFiles, workspaceFileColumns)).values(copyRows) + await tx.insert(workspaceFiles).values(copyRows) for (const source of rows) { const targetId = idMap.get(source.id) if (!targetId) continue diff --git a/apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts b/apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts index e09f1736cbd..378ff6ebd65 100644 --- a/apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts +++ b/apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts @@ -1,5 +1,5 @@ import { db } from '@sim/db' -import { type WorkspaceFileRow, workspaceFileColumns, workspaceFiles } from '@sim/db/schema' +import { type WorkspaceFileRow, workspaceFiles } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { and, asc, desc, eq, isNull, or } from 'drizzle-orm' @@ -115,7 +115,7 @@ export async function findMothershipUploadRowByChatAndName( fileName: string ): Promise { const exactRows = await db - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( and( @@ -136,7 +136,7 @@ export async function findMothershipUploadRowByChatAndName( } const allRows = await db - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( and( @@ -157,7 +157,7 @@ export async function findMothershipUploadRowByChatAndName( export async function listChatUploads(chatId: string): Promise { try { const rows = await db - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( and( 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 9a86b0f3415..fb1f2ffdd7c 100644 --- a/apps/sim/lib/credentials/__integration__/organization-personal-tokens.integration.ts +++ b/apps/sim/lib/credentials/__integration__/organization-personal-tokens.integration.ts @@ -1,13 +1,11 @@ /** Real storage, encryption, migration, and authorization; no external GitLab calls. */ import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import { credential, credentialGroup, credentialGroupEnrollment, member, organization, - organizationColumns, permissions, resourcePolicy, user, @@ -97,7 +95,7 @@ describe('organization personal tokens', () => { updatedAt: now, })) ) - await db.insert(withInsertColumns(organization, organizationColumns)).values( + await db.insert(organization).values( [ids.org, ids.foreignOrg].map((id) => ({ id, name: 'Token fixture organization', diff --git a/apps/sim/lib/data-drains/sources/workflow-logs.ts b/apps/sim/lib/data-drains/sources/workflow-logs.ts index 5d198a435c7..a0f7b9e14ae 100644 --- a/apps/sim/lib/data-drains/sources/workflow-logs.ts +++ b/apps/sim/lib/data-drains/sources/workflow-logs.ts @@ -1,5 +1,5 @@ import { dbReplica } from '@sim/db' -import { workflowExecutionLogColumns, workflowExecutionLogs } from '@sim/db/schema' +import { workflowExecutionLogs } from '@sim/db/schema' import { and, inArray, isNotNull } from 'drizzle-orm' import { MATERIALIZE_CONCURRENCY, mapWithConcurrency } from '@/lib/core/utils/concurrency' import { @@ -35,7 +35,7 @@ async function* pages(input: SourcePageInput): AsyncIterable { ) const rows = await dbReplica - .select(workflowExecutionLogColumns) + .select() .from(workflowExecutionLogs) .where( and( 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 6cbe10aeed6..e7f4b4afc69 100644 --- a/apps/sim/lib/knowledge/__integration__/execution-archive-provenance.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/execution-archive-provenance.integration.ts @@ -5,7 +5,6 @@ import { tmpdir } from 'node:os' import path from 'node:path' import type { DelegatedPrincipal } from '@sim/auth/principal' import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import { document, documentSecretProvenance, @@ -16,7 +15,6 @@ import { userTableRowSecretProvenance, userTableRows, workspace, - workspaceFileColumns, workspaceFiles, } from '@sim/db/schema' import { generateId } from '@sim/utils/id' @@ -387,7 +385,7 @@ describe('execution archive durable provenance', () => { .set({ deletedAt: new Date() }) .where(eq(workspaceFiles.key, file.key)) } else { - await db.insert(withInsertColumns(workspaceFiles, workspaceFileColumns)).values({ + await db.insert(workspaceFiles).values({ id: generateId(), key: file.key, userId: ids.aliceId, 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 0c0665585b9..df6b7ca6b22 100644 --- a/apps/sim/lib/knowledge/__integration__/external-file-provenance.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/external-file-provenance.integration.ts @@ -4,14 +4,7 @@ import { rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import path from 'node:path' import { db } from '@sim/db' -import { - knowledgeBase, - organization, - user, - workspace, - workspaceFileColumns, - workspaceFiles, -} from '@sim/db/schema' +import { knowledgeBase, organization, user, workspace, workspaceFiles } from '@sim/db/schema' import { generateId } from '@sim/utils/id' import { isPlainRecord } from '@sim/utils/object' import { eq, inArray } from 'drizzle-orm' @@ -100,10 +93,7 @@ async function parse(ids: Fixture, filePath: string, headers?: Record { - const [record] = await db - .select(workspaceFileColumns) - .from(workspaceFiles) - .where(eq(workspaceFiles.key, file.key)) + const [record] = await db.select().from(workspaceFiles).where(eq(workspaceFiles.key, file.key)) if (!record || record.context !== 'execution') { throw new Error('Parser copy has no canonical execution metadata') } diff --git a/apps/sim/lib/knowledge/__integration__/organization-mcp-search.integration.ts b/apps/sim/lib/knowledge/__integration__/organization-mcp-search.integration.ts index 0b7060df4c6..8f8374ebb9f 100644 --- a/apps/sim/lib/knowledge/__integration__/organization-mcp-search.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/organization-mcp-search.integration.ts @@ -12,7 +12,6 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/ import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js' import type { Principal } from '@sim/auth/principal' import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import { apiKey, document, @@ -26,7 +25,6 @@ import { oauthClient, oauthConsent, organization, - organizationColumns, organizationSearchIntegration, rateLimitBucket, user, @@ -254,7 +252,7 @@ describe('organization Search MCP with real ingestion and current access', () => updatedAt: new Date(), })) ) - await db.insert(withInsertColumns(organization, organizationColumns)).values({ + await db.insert(organization).values({ id: otherOrganizationId, name: 'Other organization MCP fixture', slug: otherOrganizationId, 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 22e90269614..3c0749defc0 100644 --- a/apps/sim/lib/knowledge/__integration__/seed-source-access-fixture.ts +++ b/apps/sim/lib/knowledge/__integration__/seed-source-access-fixture.ts @@ -1,6 +1,5 @@ import { createHash } from 'node:crypto' import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import { credential, credentialGroup, @@ -12,7 +11,6 @@ import { knowledgeExternalGroup, knowledgeExternalGroupMember, organization, - organizationColumns, permissions, user, workspace, @@ -71,7 +69,7 @@ export async function seedKnowledgeAclFixture( updatedAt: now, }, ]) - await db.insert(withInsertColumns(organization, organizationColumns)).values({ + await db.insert(organization).values({ id: ids.organizationId, name: 'ACL integration organization', slug: ids.organizationId, diff --git a/apps/sim/lib/knowledge/__integration__/slack-search-turns.integration.ts b/apps/sim/lib/knowledge/__integration__/slack-search-turns.integration.ts index 91e6301593a..740cfa9fc69 100644 --- a/apps/sim/lib/knowledge/__integration__/slack-search-turns.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/slack-search-turns.integration.ts @@ -1,12 +1,10 @@ /** Exercises real PostgreSQL locks and constraints using only isolated, explicitly cleaned fixtures. */ import type { OrganizationDelegatedPrincipal } from '@sim/auth/principal' import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import { copilotChats, credential, organization, - organizationColumns, outboxEvent, slackSearchInstallation, slackSearchTurn, @@ -74,7 +72,7 @@ describe('durable Slack Search turns in PostgreSQL', () => { })) ) await db - .insert(withInsertColumns(organization, organizationColumns)) + .insert(organization) .values({ id: organizationId, name: 'Slack queue fixture', slug: organizationId }) await db.insert(credential).values({ id: credentialId, 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 381c32a1a2b..54db3536ac7 100644 --- a/apps/sim/lib/knowledge/__integration__/upload-read-provenance.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/upload-read-provenance.integration.ts @@ -10,7 +10,6 @@ import { organization, user, workspace, - workspaceFileColumns, workspaceFiles, } from '@sim/db/schema' import { generateId } from '@sim/utils/id' @@ -79,10 +78,7 @@ async function seedUpload(provenance?: WorkspaceFileSecretProvenance) { 'text/plain', CONTENT.length ) - const [file] = await db - .select(workspaceFileColumns) - .from(workspaceFiles) - .where(eq(workspaceFiles.key, key)) + const [file] = await db.select().from(workspaceFiles).where(eq(workspaceFiles.key, key)) if (provenance) { await db.transaction((tx) => replaceWorkspaceFileSecretProvenanceInTx(tx, file.id, file.contentUpdatedAt, provenance) diff --git a/apps/sim/lib/logs/execution/logger.ts b/apps/sim/lib/logs/execution/logger.ts index 45d53d4764c..10900148baf 100644 --- a/apps/sim/lib/logs/execution/logger.ts +++ b/apps/sim/lib/logs/execution/logger.ts @@ -1,11 +1,9 @@ import { db, dbFor } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' import { organization, usageLog, user as userTable, workflow, - workflowExecutionLogColumns, workflowExecutionLogs, workspace, } from '@sim/db/schema' @@ -653,7 +651,7 @@ export class ExecutionLogger implements IExecutionLoggerService { // Check if execution log already exists (idempotency check) const existingLog = await execDb - .select(workflowExecutionLogColumns) + .select() .from(workflowExecutionLogs) .where(eq(workflowExecutionLogs.executionId, executionId)) .limit(1) @@ -699,7 +697,7 @@ export class ExecutionLogger implements IExecutionLoggerService { const startTime = new Date() const [workflowLog] = await execDb - .insert(withInsertColumns(workflowExecutionLogs, workflowExecutionLogColumns)) + .insert(workflowExecutionLogs) .values({ id: generateId(), workflowId, @@ -724,7 +722,7 @@ export class ExecutionLogger implements IExecutionLoggerService { traceSpanCount: 0, }, }) - .returning(workflowExecutionLogColumns) + .returning() execLog.debug('Created workflow log', { logId: workflowLog.id }) @@ -958,7 +956,7 @@ export class ExecutionLogger implements IExecutionLoggerService { execLog.debug('Completing workflow execution', { isResume }) const [existingLog] = await execDb - .select(workflowExecutionLogColumns) + .select() .from(workflowExecutionLogs) .where(eq(workflowExecutionLogs.executionId, executionId)) .limit(1) @@ -1206,11 +1204,11 @@ export class ExecutionLogger implements IExecutionLoggerService { : sql`${workflowExecutionLogs.status} != 'cancelled'` ) ) - .returning(workflowExecutionLogColumns) + .returning() if (!log) { const [currentLog] = await tx - .select(workflowExecutionLogColumns) + .select() .from(workflowExecutionLogs) .where(eq(workflowExecutionLogs.executionId, executionId)) .limit(1) @@ -1445,7 +1443,7 @@ export class ExecutionLogger implements IExecutionLoggerService { async getWorkflowExecution(executionId: string): Promise { const [workflowLog] = await execDb - .select(workflowExecutionLogColumns) + .select() .from(workflowExecutionLogs) .where(eq(workflowExecutionLogs.executionId, executionId)) .limit(1) diff --git a/apps/sim/lib/public-shares/share-manager.ts b/apps/sim/lib/public-shares/share-manager.ts index 52367728707..1d9cd3caa09 100644 --- a/apps/sim/lib/public-shares/share-manager.ts +++ b/apps/sim/lib/public-shares/share-manager.ts @@ -1,12 +1,5 @@ import { db } from '@sim/db' -import { - publicShare, - user, - type WorkspaceFileRow, - workspace, - workspaceFileColumns, - workspaceFiles, -} from '@sim/db/schema' +import { publicShare, user, type WorkspaceFileRow, workspace, workspaceFiles } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId, generateShortId } from '@sim/utils/id' import { and, eq, inArray, isNull } from 'drizzle-orm' @@ -276,7 +269,7 @@ export async function resolveActiveShareByToken(token: string): Promise { for (const table of ['user', 'member', 'organization', 'upload_session']) { await connection`CREATE TABLE ${connection(table)} (LIKE ${connection(`public.${table}`)} INCLUDING ALL)` } - await db.insert(withInsertColumns(organization, organizationColumns)).values({ + await db.insert(organization).values({ id: organizationId, name: 'Logo test organization', slug: generateId(), diff --git a/apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts b/apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts index 33221719dea..b76fbdc7a17 100644 --- a/apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts +++ b/apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts @@ -5,14 +5,7 @@ import { randomBytes } from 'crypto' import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' -import { - uploadSession, - type WorkspaceFileRow, - workspace, - workspaceFileColumns, - workspaceFiles, -} from '@sim/db/schema' +import { uploadSession, type WorkspaceFileRow, workspace, workspaceFiles } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { describeError, @@ -267,7 +260,7 @@ async function insertWorkspaceFileMetadataInTx( metadata: WorkspaceFileMetadataInsert ): Promise { const [inserted] = await tx - .insert(withInsertColumns(workspaceFiles, workspaceFileColumns)) + .insert(workspaceFiles) .values({ ...omit(metadata, ['size']), sizeBytes: metadata.size, @@ -280,7 +273,7 @@ async function insertWorkspaceFileMetadataInTx( contentUpdatedAt: new Date(), }) .onConflictDoNothing() - .returning(workspaceFileColumns) + .returning() return inserted } @@ -298,7 +291,7 @@ async function findWorkspaceFileByRegistrationKey( key: string ): Promise { const files = await executor - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where(eq(workspaceFiles.key, key)) .orderBy(sql`${workspaceFiles.deletedAt} IS NULL DESC`) @@ -315,7 +308,7 @@ async function findWorkspaceFileForLifecycle( fileId: string ): Promise { const [file] = await executor - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( and( @@ -1057,7 +1050,7 @@ export async function trackChatUpload( await db.transaction(async (tx) => { const [inserted] = await tx - .insert(withInsertColumns(workspaceFiles, workspaceFileColumns)) + .insert(workspaceFiles) .values({ id: fileId, key: s3Key, @@ -1223,7 +1216,7 @@ export async function getWorkspaceFileByName( ): Promise { const folderId = options?.folderId ?? null const files = await db - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( and( @@ -1647,7 +1640,7 @@ export async function getWorkspaceFile( try { const { includeDeleted = false } = options ?? {} const files = await db - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( includeDeleted @@ -1837,7 +1830,7 @@ export async function updateWorkspaceFileContent( try { finalized = await db.transaction(async (tx) => { const [currentFile] = await tx - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( and( @@ -1906,7 +1899,7 @@ export async function updateWorkspaceFileContent( isNull(workspaceFiles.deletedAt) ) ) - .returning(workspaceFileColumns) + .returning() if (!updatedFile) { throw new OrchestrationError('not_found', 'File not found or could not be updated') } @@ -2203,7 +2196,7 @@ export async function deleteWorkspaceFile(workspaceId: string, fileId: string): isNull(workspaceFiles.deletedAt) ) ) - .returning(workspaceFileColumns) + .returning() if (!archived) return logger.info(`Successfully archived workspace file: ${archived.originalName}`) @@ -2352,7 +2345,7 @@ export async function restoreWorkspaceFile(workspaceId: string, fileId: string): isNotNull(workspaceFiles.deletedAt) ) ) - .returning(workspaceFileColumns) + .returning() if (!restored) return logger.info(`Successfully restored workspace file: ${newName}`) diff --git a/apps/sim/lib/uploads/server/metadata.ts b/apps/sim/lib/uploads/server/metadata.ts index 97ab3565e38..c80b1228d0d 100644 --- a/apps/sim/lib/uploads/server/metadata.ts +++ b/apps/sim/lib/uploads/server/metadata.ts @@ -1,6 +1,5 @@ import { db } from '@sim/db' -import { withInsertColumns } from '@sim/db/insert-columns' -import { type WorkspaceFileRow, workspaceFileColumns, workspaceFiles } from '@sim/db/schema' +import { type WorkspaceFileRow, workspaceFiles } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId } from '@sim/utils/id' import { and, desc, eq, inArray, isNotNull, isNull, sql } from 'drizzle-orm' @@ -95,7 +94,7 @@ async function findActiveFileMetadataByKey( key: string ): Promise { const [record] = await executor - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where(and(eq(workspaceFiles.key, key), isNull(workspaceFiles.deletedAt))) /** Wait for in-flight cleanup before accepting an active identity for newly uploaded bytes. */ @@ -141,7 +140,7 @@ async function insertFileMetadataWithExecutor( } const [existingDeleted] = await executor - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where(and(eq(workspaceFiles.key, key), isNotNull(workspaceFiles.deletedAt))) .limit(1) @@ -167,7 +166,7 @@ async function insertFileMetadataWithExecutor( contentUpdatedAt: sql`GREATEST(CURRENT_TIMESTAMP, ${workspaceFiles.contentUpdatedAt} + INTERVAL '1 millisecond')`, }) .where(and(eq(workspaceFiles.id, existingDeleted.id), isNotNull(workspaceFiles.deletedAt))) - .returning(workspaceFileColumns) + .returning() if (restored) { return restored @@ -180,7 +179,7 @@ async function insertFileMetadataWithExecutor( try { const [inserted] = await executor - .insert(withInsertColumns(workspaceFiles, workspaceFileColumns)) + .insert(workspaceFiles) .values({ id: fileId, key, @@ -196,7 +195,7 @@ async function insertFileMetadataWithExecutor( deletedAt: null, uploadedAt: new Date(), }) - .returning(workspaceFileColumns) + .returning() if (!inserted) { throw new Error(`Failed to insert file metadata for key: ${key}`) @@ -236,7 +235,7 @@ async function insertImmutableFileMetadataWithExecutor( } = options assertFileMetadataOrganizationOwner(options) const [inserted] = await executor - .insert(withInsertColumns(workspaceFiles, workspaceFileColumns)) + .insert(workspaceFiles) .values({ id: id || generateId(), key, @@ -253,7 +252,7 @@ async function insertImmutableFileMetadataWithExecutor( uploadedAt: new Date(), }) .onConflictDoNothing() - .returning(workspaceFileColumns) + .returning() if (inserted) return inserted @@ -317,7 +316,7 @@ export async function insertFileMetadataMany( const uniqueRows = [...uniqueRowsByKey.values()] const inserted = await db - .insert(withInsertColumns(workspaceFiles, workspaceFileColumns)) + .insert(workspaceFiles) .values( uniqueRows.map((row) => ({ id: row.id || generateId(), @@ -336,13 +335,13 @@ export async function insertFileMetadataMany( })) ) .onConflictDoNothing() - .returning(workspaceFileColumns) + .returning() const insertedKeys = new Set(inserted.map((record) => record.key)) const conflictingRows = uniqueRows.filter((row) => !insertedKeys.has(row.key)) if (conflictingRows.length > 0) { const activeRows = await db - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( and( @@ -384,7 +383,7 @@ export async function getFileMetadataByKey( } const [record] = await db - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where(conditions.length > 1 ? and(...conditions) : conditions[0]) // Prefer the active row when includeDeleted lets both an active and a @@ -439,7 +438,7 @@ export async function getFileMetadataByKeys( } if (options?.includeDeleted) { return executor - .selectDistinctOn([workspaceFiles.key], workspaceFileColumns) + .selectDistinctOn([workspaceFiles.key]) .from(workspaceFiles) .where(and(inArray(workspaceFiles.key, keys), eq(workspaceFiles.context, context))) .orderBy( @@ -450,7 +449,7 @@ export async function getFileMetadataByKeys( ) } const query = executor - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where( and( @@ -473,7 +472,7 @@ export async function getFileMetadataById( const conditions = [eq(workspaceFiles.id, id)] if (!includeDeleted) conditions.push(isNull(workspaceFiles.deletedAt)) const [record] = await db - .select(workspaceFileColumns) + .select() .from(workspaceFiles) .where(conditions.length > 1 ? and(...conditions) : conditions[0]) .limit(1) diff --git a/packages/db/insert-columns.test.ts b/packages/db/insert-columns.test.ts deleted file mode 100644 index 88abafdd1cd..00000000000 --- a/packages/db/insert-columns.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { withInsertColumns } from '@sim/db/insert-columns' -import { - organization, - organizationColumns, - userStats, - userStatsColumns, - workflowExecutionLogColumns, - workflowExecutionLogs, - workspaceFileColumns, - workspaceFiles, -} from '@sim/db/schema' -import { getTableColumns, getTableName, sql } from 'drizzle-orm' -import { getTableConfig, type PgTable, pgSchema, text } from 'drizzle-orm/pg-core' -import { drizzle } from 'drizzle-orm/pg-proxy' -import { describe, expect, expectTypeOf, it, vi } from 'vitest' - -const db = drizzle(async () => ({ rows: [] })) - -describe('withInsertColumns', () => { - it.each([ - { table: userStats, columns: userStatsColumns, retired: 'total_manual_executions' }, - { table: organization, columns: organizationColumns, retired: 'departed_member_usage' }, - { table: workflowExecutionLogs, columns: workflowExecutionLogColumns, retired: 'cost' }, - { table: workspaceFiles, columns: workspaceFileColumns, retired: 'size' }, - ])('excludes every retired column from $retired inserts and RETURNING', ({ table, columns }) => { - const originalColumns = getTableColumns(table) - const originalConfig = getTableConfig(table) - const target = withInsertColumns(table, columns) - const query = db.insert(target).values({ id: 'example' }).returning().toSQL() - const fullQuery = db.insert(table).values({ id: 'example' }).returning().toSQL() - - for (const [key, column] of Object.entries(originalColumns)) { - expect(fullQuery.sql).toContain(`"${column.name}"`) - if (key in columns) expect(query.sql).toContain(`"${column.name}"`) - else expect(query.sql).not.toContain(`"${column.name}"`) - } - expect(getTableName(target)).toBe(getTableName(table)) - expect(getTableColumns(target)).toBe(columns) - expect(getTableColumns(table)).toBe(originalColumns) - expect(getTableConfig(table)).toEqual(originalConfig) - }) - - it('retains live insert types and excludes retired fields', () => { - const target = withInsertColumns(userStats, userStatsColumns) - type Insert = typeof target.$inferInsert - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf() - expectTypeOf().toEqualTypeOf< - 'payment_failed' | 'dispute' | null | undefined - >() - expectTypeOf<'totalManualExecutions'>().not.toExtend() - expectTypeOf<'currentPeriodCost'>().not.toExtend() - }) - - it('preserves bulk values, explicit nulls, defaults, and conflict handling', () => { - const target = withInsertColumns(userStats, userStatsColumns) - const query = db - .insert(target) - .values([ - { id: 'stats-1', userId: 'user-1', currentUsageLimit: null }, - { id: 'stats-2', userId: 'user-2', currentUsageLimit: '5' }, - ]) - .onConflictDoUpdate({ - target: userStats.userId, - set: { currentUsageLimit: sql`excluded.current_usage_limit` }, - }) - .returning({ id: userStats.id }) - .toSQL() - - expect(query.params).toEqual(['stats-1', 'user-1', null, 'stats-2', 'user-2', '5']) - expect(query.sql).toContain('default') - expect(query.sql).toContain( - 'on conflict ("user_id") do update set "current_usage_limit" = excluded.current_usage_limit' - ) - expect(query.sql).toContain('returning "id"') - expect(query.sql).not.toContain('total_manual_executions') - expect( - db.insert(target).values({ id: 'stats-3', userId: 'user-3' }).onConflictDoNothing().toSQL() - .sql - ).toContain('on conflict do nothing') - }) - - it('preserves parameter encoders and RETURNING decoders', async () => { - const startedAt = new Date('2026-01-01T00:00:00Z') - const payload = { sample: true } - const execute = vi.fn(async () => ({ - rows: [['2026-01-01 00:00:00', payload, '{model-a,model-b}']], - })) - const connection = drizzle(execute) - const rows = await connection - .insert(withInsertColumns(workflowExecutionLogs, workflowExecutionLogColumns)) - .values({ - id: 'log-1', - workflowId: 'workflow-1', - workspaceId: 'workspace-1', - executionId: 'execution-1', - stateSnapshotId: 'snapshot-1', - level: 'info', - status: 'running', - trigger: 'manual', - startedAt, - executionData: payload, - modelsUsed: ['model-a', 'model-b'], - }) - .returning({ - startedAt: workflowExecutionLogs.startedAt, - executionData: workflowExecutionLogs.executionData, - modelsUsed: workflowExecutionLogs.modelsUsed, - }) - - expect(rows).toEqual([ - { startedAt, executionData: payload, modelsUsed: ['model-a', 'model-b'] }, - ]) - expect(execute).toHaveBeenCalledWith( - expect.not.stringContaining('"cost"'), - expect.arrayContaining([ - startedAt.toISOString(), - JSON.stringify(payload), - '{"model-a","model-b"}', - ]), - 'all', - expect.arrayContaining(['timestamp', 'json']) - ) - }) - - it('retains schema-qualified names and runtime defaults', () => { - const table = pgSchema('insert_test').table('records', { - id: text('id').$defaultFn(() => 'generated-id'), - retired: text('retired'), - }) - const query = db - .insert(withInsertColumns(table, { id: table.id })) - .values({}) - .toSQL() - expect(query.sql).toBe('insert into "insert_test"."records" ("id") values ($1)') - expect(query.params).toEqual(['generated-id']) - }) - - it('rejects a column from a different table', () => { - const table: PgTable = userStats - expect(() => withInsertColumns(table, { id: organization.id })).toThrow( - 'INSERT column id does not belong to the target table' - ) - }) -}) diff --git a/packages/db/insert-columns.ts b/packages/db/insert-columns.ts deleted file mode 100644 index 065b47c1b1e..00000000000 --- a/packages/db/insert-columns.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { getTableColumns } from 'drizzle-orm' -import type { PgTable } from 'drizzle-orm/pg-core' - -type InsertTable = PgTable<{ - name: TTable['_']['name'] - schema: TTable['_']['schema'] - dialect: TTable['_']['config']['dialect'] - columns: Pick -}> - -/** - * Restricts Drizzle's INSERT column list without changing the migration schema. - * Omitting a value is insufficient: Drizzle still names that column with DEFAULT. - * The table proxy substitutes the column map exposed by getTableColumns while - * retaining table metadata, column codecs, defaults, and SQL names. - * It is local to this insert and never mutates the shared table or its columns. - */ -export function withInsertColumns< - TTable extends PgTable, - TKey extends keyof TTable['_']['columns'], ->(table: TTable, columns: Pick): InsertTable { - const declaredColumns = getTableColumns(table) - for (const [name, column] of Object.entries(columns)) { - if (declaredColumns[name] !== column) { - throw new Error(`INSERT column ${name} does not belong to the target table`) - } - } - - return new Proxy(table, { - get(target, property, receiver) { - const value = Reflect.get(target, property, receiver) - return value === declaredColumns ? columns : value - }, - }) as InsertTable -} diff --git a/packages/db/migrations/0348_drop_retired_usage_columns.sql b/packages/db/migrations/0348_drop_retired_usage_columns.sql new file mode 100644 index 00000000000..de05084a563 --- /dev/null +++ b/packages/db/migrations/0348_drop_retired_usage_columns.sql @@ -0,0 +1,82 @@ +-- Installations upgrading past the prep release must finish its backfills first. +-- Check migration receipts instead of scanning the execution-log table during DDL. +-- Empty databases can apply the entire migration history in one pass. +DO $$ +DECLARE + size_backfilled boolean := false; + cost_backfilled boolean := false; +BEGIN + IF to_regclass('script_migrations') IS NOT NULL THEN + SELECT EXISTS ( + SELECT 1 FROM script_migrations + WHERE name = '0008_backfill_workspace_file_size_bytes' + ) INTO size_backfilled; + SELECT EXISTS ( + SELECT 1 FROM script_migrations + WHERE name = '0009_backfill_wel_residual_cost_total' + ) INTO cost_backfilled; + END IF; + + IF NOT size_backfilled + AND EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'workspace_files'::regclass AND attname = 'size' AND NOT attisdropped) + AND EXISTS (SELECT 1 FROM workspace_files) + THEN + RAISE EXCEPTION 'Run the v0.8.38 db:migrate command to complete 0008_backfill_workspace_file_size_bytes before dropping workspace_files.size'; + END IF; + IF NOT cost_backfilled + AND EXISTS (SELECT 1 FROM pg_attribute WHERE attrelid = 'workflow_execution_logs'::regclass AND attname = 'cost' AND NOT attisdropped) + AND EXISTS (SELECT 1 FROM workflow_execution_logs) + THEN + RAISE EXCEPTION 'Run the v0.8.38 db:migrate command to complete 0009_backfill_wel_residual_cost_total before dropping workflow_execution_logs.cost'; + END IF; +END; +$$;--> statement-breakpoint + +-- The v0.8.38 application writes size_bytes; the legacy size bridge is no longer needed. +DROP TRIGGER IF EXISTS "workspace_files_sync_size_columns" ON "workspace_files";--> statement-breakpoint +DROP FUNCTION IF EXISTS "sync_workspace_file_size_columns"();--> statement-breakpoint + +-- migration-safe: contract of #7134, #7774, and #7813 (v0.8.38): application and Better Auth SQL exclude departed_member_usage. +ALTER TABLE "organization" DROP COLUMN IF EXISTS "departed_member_usage";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_manual_executions";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_api_calls";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_webhook_triggers";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_scheduled_executions";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_chat_executions";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_mcp_executions";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_tokens_used";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_cost";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "current_period_cost";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "pro_period_cost_snapshot";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "pro_period_cost_snapshot_at";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_copilot_cost";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "current_period_copilot_cost";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_copilot_tokens";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_copilot_calls";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_mcp_copilot_calls";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "total_mcp_copilot_cost";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "current_period_mcp_copilot_cost";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): user_stats reads and inserts exclude retired usage counters. +ALTER TABLE "user_stats" DROP COLUMN IF EXISTS "last_active";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): log reads and inserts exclude cost; cost_total backfill completed before drop. +ALTER TABLE "workflow_execution_logs" DROP COLUMN IF EXISTS "cost";--> statement-breakpoint +-- migration-safe: contract of #7134 and #7774 (v0.8.38): all file reads and inserts use size_bytes; backfill completed before drop. +ALTER TABLE "workspace_files" DROP COLUMN IF EXISTS "size"; diff --git a/packages/db/migrations/meta/0348_snapshot.json b/packages/db/migrations/meta/0348_snapshot.json new file mode 100644 index 00000000000..1e535bb04be --- /dev/null +++ b/packages/db/migrations/meta/0348_snapshot.json @@ -0,0 +1,26694 @@ +{ + "id": "7f02eafd-2b34-42e0-a89e-8a205c612f13", + "prevId": "fb431e54-4e75-43c0-a69c-ad79ac1c1b23", + "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.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 + }, + "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_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": 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.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_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_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_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_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": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "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_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_vector_hnsw_idx": { + "name": "embedding_384_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_vector_hnsw_idx": { + "name": "embedding_768_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_vector_hnsw_idx": { + "name": "embedding_1024_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_vector_hnsw_idx": { + "name": "embedding_3072_vector_hnsw_idx", + "columns": [ + { + "expression": "(\"embedding_3072\"::halfvec(3072)) halfvec_cosine_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_binary_hnsw_idx": { + "name": "embedding_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding\")::bit(1536)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_binary_hnsw_idx": { + "name": "embedding_384_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_384\")::bit(384)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_binary_hnsw_idx": { + "name": "embedding_768_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_768\")::bit(768)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_binary_hnsw_idx": { + "name": "embedding_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_1024\")::bit(1024)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_binary_hnsw_idx": { + "name": "embedding_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_3072\")::bit(3072)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "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": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_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_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 + }, + "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_binary_hnsw_idx": { + "name": "embedding_search_binary_hnsw_idx", + "columns": [ + { + "expression": "binary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_384_binary_hnsw_idx": { + "name": "embedding_search_384_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_768_binary_hnsw_idx": { + "name": "embedding_search_768_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_1024_binary_hnsw_idx": { + "name": "embedding_search_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_3072_binary_hnsw_idx": { + "name": "embedding_search_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "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", + "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 + }, + "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", + "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 + } + }, + "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 + }, + "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_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_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 + } + }, + "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')" + } + }, + "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 + } + }, + "indexes": { + "kcsl_connector_started_at_idx": { + "name": "kcsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcsl_started_at_partial_idx": { + "name": "kcsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector_sync_log\".\"status\" = 'started'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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.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 + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_secret_provenance": { + "name": "memory_secret_provenance", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memory_secret_provenance_memory_id_memory_id_fk": { + "name": "memory_secret_provenance_memory_id_memory_id_fk", + "tableFrom": "memory_secret_provenance", + "tableTo": "memory", + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_secret_provenance_status_check": { + "name": "memory_secret_provenance_status_check", + "value": "\"memory_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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 + }, + "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_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_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.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_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.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_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_period_cost_idx": { + "name": "usage_log_billing_period_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_created_at_cost_idx": { + "name": "usage_log_billing_entity_created_at_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_deadline_idx": { + "name": "workflow_execution_logs_running_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_started_at_idx": { + "name": "workflow_execution_logs_redacting_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'redacting'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_deadline_idx": { + "name": "workflow_execution_logs_redacting_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "mounted_secrets": { + "name": "mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_operation", + "columnsFrom": ["deployment_operation_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_secret_scope": { + "name": "inbox_secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "inbox_mounted_secrets": { + "name": "inbox_mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_inbox_provider_id_idx": { + "name": "workspace_inbox_provider_id_idx", + "columns": [ + { + "expression": "inbox_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_storage_used_bytes_non_negative": { + "name": "workspace_storage_used_bytes_non_negative", + "value": "\"workspace\".\"storage_used_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_collab_state": { + "name": "workspace_file_collab_state", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "doc_state": { + "name": "doc_state", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_collab_state_file_id_workspace_files_id_fk": { + "name": "workspace_file_collab_state_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_collab_state", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_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_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_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_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": "now()" + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_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 + } + }, + "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_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 2f1985d0f9b..90f19ecc792 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2430,6 +2430,13 @@ "when": 1789436120319, "tag": "0347_sso_domain_primary_provider", "breakpoints": true + }, + { + "idx": 348, + "version": "7", + "when": 1789514921671, + "tag": "0348_drop_retired_usage_columns", + "breakpoints": true } ] } diff --git a/packages/db/package.json b/packages/db/package.json index 68810495b41..df53ffeeaad 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -17,10 +17,6 @@ "types": "./schema.ts", "default": "./schema.ts" }, - "./insert-columns": { - "types": "./insert-columns.ts", - "default": "./insert-columns.ts" - }, "./timestamps": { "types": "./timestamps.ts", "default": "./timestamps.ts" @@ -32,9 +28,8 @@ }, "scripts": { "db:generate": "bunx drizzle-kit generate --config=./drizzle.config.ts", - "db:push": "bunx drizzle-kit push --config=./drizzle.config.ts && bun --env-file=.env run ./scripts/reconcile-credential-group-resource-policies.ts && bun --env-file=.env run ./scripts/reconcile-oauth-provider.ts && bun --env-file=.env run ./script-migrations/0016_backfill_search_vectors.ts", + "db:push": "bun --env-file=.env run ./scripts/push.ts", "db:migrate": "bun --env-file=.env run ./scripts/migrate.ts", - "db:apply-dev-workspace-file-size-cutover": "bun --env-file=.env run ./scripts/apply-dev-workspace-file-size-cutover.ts", "db:reconcile-fork-kb-file-ownership": "bun --env-file=.env run ./scripts/reconcile-fork-kb-file-ownership.ts", "db:reconcile-workspace-storage": "bun --env-file=.env run ./scripts/reconcile-workspace-storage.ts", "db:studio": "bunx drizzle-kit studio --config=./drizzle.config.ts", diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 5653622af0c..2a1922ef0e5 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -1,5 +1,4 @@ -import { omit } from '@sim/utils/object' -import { getTableColumns, type SQL, sql } from 'drizzle-orm' +import { type SQL, sql } from 'drizzle-orm' import { type AnyPgColumn, bigint, @@ -525,15 +524,6 @@ export const workflowExecutionLogs = pgTable( * `materializeExecutionData`, which resolves the pointer. */ executionData: jsonb('execution_data').notNull().default('{}'), - /** - * contract-pending(after #7134 and #7774 are fully deployed): - * DROP cost. Reads and inserts use workflowExecutionLogColumns. Before the - * drop, confirm script migration 0009_backfill_wel_residual_cost_total has - * projected all residual numeric totals, then deregister it in the contract - * PR because it reads this column. - */ - /** @deprecated Not written/read; cost lives in usage_log + the `cost_total` projection. */ - cost: jsonb('cost'), // Faithful, write-once projection of the run's usage_log ledger sum (dollars). // Backs list cost display/filter/sort without live aggregation; never an // independently-computed value (cost_total == SUM(usage_log) for the run). @@ -598,12 +588,6 @@ export const workflowExecutionLogs = pgTable( }) ) -/** - * Live columns of `workflow_execution_logs` while the `cost` drop is - * outstanding — see `userStatsColumns` for the pattern. - */ -export const workflowExecutionLogColumns = omit(getTableColumns(workflowExecutionLogs), ['cost']) - export const executionLargeValueReferenceSourceEnum = pgEnum( 'execution_large_value_reference_source', ['execution_log', 'paused_snapshot'] @@ -1250,36 +1234,8 @@ export const userStats = pgTable('user_stats', { .notNull() .references(() => user.id, { onDelete: 'cascade' }) .unique(), // One record per user - /** - * contract-pending(after #7134 and #7774 are fully deployed): - * DROP the 19 deprecated columns. Usage updates were retired by #7078/#7113; - * #7134 removed the remaining reads. Declarations stay until the contract so - * generated migrations match the deployed database. Reads use userStatsColumns; - * inserts use withInsertColumns(userStats, userStatsColumns), because omitting - * values still makes Drizzle name the columns with DEFAULT. The pending-drop - * audit enforces both. Deploy the compatibility release to every writer before - * deleting these declarations and generating the DROP migration. - */ - /** @deprecated Retired usage counter; derive from usage_log. */ - totalManualExecutions: integer('total_manual_executions').notNull().default(0), - /** @deprecated Retired usage counter; derive from usage_log. */ - totalApiCalls: integer('total_api_calls').notNull().default(0), - /** @deprecated Retired usage counter; derive from usage_log. */ - totalWebhookTriggers: integer('total_webhook_triggers').notNull().default(0), - /** @deprecated Retired usage counter; derive from usage_log. */ - totalScheduledExecutions: integer('total_scheduled_executions').notNull().default(0), - /** @deprecated Retired usage counter; derive from usage_log. */ - totalChatExecutions: integer('total_chat_executions').notNull().default(0), - /** @deprecated Retired usage counter; derive from usage_log. */ - totalMcpExecutions: integer('total_mcp_executions').notNull().default(0), - /** @deprecated Retired usage counter; derive from usage_log. */ - totalTokensUsed: bigint('total_tokens_used', { mode: 'number' }).notNull().default(0), - /** @deprecated No readers or writers; report cost from usage_log. */ - totalCost: decimal('total_cost').notNull().default('0'), currentUsageLimit: decimal('current_usage_limit').default(DEFAULT_FREE_CREDITS.toString()), // Default $5 (1,000 credits) for free plan, null for team/enterprise usageLimitUpdatedAt: timestamp('usage_limit_updated_at').defaultNow(), - /** @deprecated No readers or writers; usage is the attributed usage_log ledger. Drop via DROP COLUMN in a follow-up migration. */ - currentPeriodCost: decimal('current_period_cost').notNull().default('0'), /** Previous-period usage; written by the cycle-close sweep from ledger sums. */ lastPeriodCost: decimal('last_period_cost').default('0'), /** @@ -1290,10 +1246,6 @@ export const userStats = pgTable('user_stats', { * by the ordinary per-usage ledger write path. */ billedOverageThisPeriod: decimal('billed_overage_this_period').notNull().default('0'), // Amount of overage already billed via threshold billing - /** @deprecated No readers or writers; ledger entity stamps attribute pre/post-join usage. Drop via DROP COLUMN in a follow-up migration. */ - proPeriodCostSnapshot: decimal('pro_period_cost_snapshot').default('0'), - /** @deprecated No readers or writers; see proPeriodCostSnapshot. Drop via DROP COLUMN in a follow-up migration. */ - proPeriodCostSnapshotAt: timestamp('pro_period_cost_snapshot_at'), /** * Credit balance tracker. * @@ -1301,22 +1253,8 @@ export const userStats = pgTable('user_stats', { * overage collection. It is not a per-usage aggregate counter. */ creditBalance: decimal('credit_balance').notNull().default('0'), - /** @deprecated No readers or writers; report Copilot cost from usage_log. */ - totalCopilotCost: decimal('total_copilot_cost').notNull().default('0'), - /** @deprecated No readers or writers; Copilot usage is the copilot-source usage_log ledger. Drop via DROP COLUMN in a follow-up migration. */ - currentPeriodCopilotCost: decimal('current_period_copilot_cost').notNull().default('0'), /** Previous-period Copilot cost; written by the cycle-close sweep from copilot-source ledger sums. */ lastPeriodCopilotCost: decimal('last_period_copilot_cost').default('0'), - /** @deprecated No readers or writers; report Copilot tokens from usage_log. */ - totalCopilotTokens: bigint('total_copilot_tokens', { mode: 'number' }).notNull().default(0), - /** @deprecated No readers or writers; report Copilot calls from usage_log. */ - totalCopilotCalls: integer('total_copilot_calls').notNull().default(0), - /** @deprecated No readers or writers; report MCP Copilot calls from usage_log. */ - totalMcpCopilotCalls: integer('total_mcp_copilot_calls').notNull().default(0), - /** @deprecated No readers or writers; report MCP Copilot cost from usage_log. */ - totalMcpCopilotCost: decimal('total_mcp_copilot_cost').notNull().default('0'), - /** @deprecated No writer (never incremented or reset). MCP copilot usage lives in usage_log (source 'mcp_copilot'); read it from there, not this column. */ - currentPeriodMcpCopilotCost: decimal('current_period_mcp_copilot_cost').notNull().default('0'), /** * Storage upload/delete hot-path tracker for personal plans. * @@ -1324,8 +1262,6 @@ export const userStats = pgTable('user_stats', { * org-scoped storage writes update `organization.storageUsedBytes`. */ storageUsedBytes: bigint('storage_used_bytes', { mode: 'number' }).notNull().default(0), - /** @deprecated No readers or writers; not updated since execution stopped writing user_stats. */ - lastActive: timestamp('last_active').notNull().defaultNow(), billingBlocked: boolean('billing_blocked').notNull().default(false), billingBlockedReason: billingBlockedReasonEnum('billing_blocked_reason'), /** @@ -1345,35 +1281,6 @@ export const userStats = pgTable('user_stats', { .default({}), }) -/** - * Live columns of `user_stats` — the selection every read and withInsertColumns - * insert uses while the contract-pending drop (see the marker inside the table) is - * outstanding, so generated SQL never names the doomed columns. Enforced by - * `scripts/check-pending-drop-tables.ts`; the contract PR deletes this helper - * together with the deprecated declarations. - */ -export const userStatsColumns = omit(getTableColumns(userStats), [ - 'totalManualExecutions', - 'totalApiCalls', - 'totalWebhookTriggers', - 'totalScheduledExecutions', - 'totalChatExecutions', - 'totalMcpExecutions', - 'totalTokensUsed', - 'totalCost', - 'currentPeriodCost', - 'proPeriodCostSnapshot', - 'proPeriodCostSnapshotAt', - 'totalCopilotCost', - 'currentPeriodCopilotCost', - 'totalCopilotTokens', - 'totalCopilotCalls', - 'totalMcpCopilotCalls', - 'totalMcpCopilotCost', - 'currentPeriodMcpCopilotCost', - 'lastActive', -]) - export const customTools = pgTable( 'custom_tools', { @@ -1719,16 +1626,6 @@ export const organization = pgTable('organization', { .$type>() .notNull() .default({}), - /** - * contract-pending(after #7134, #7774, and the Better Auth schema projection are fully deployed): - * DROP departed_member_usage. Application reads and inserts use - * organizationColumns; createSimAuthAdapter also projects the table for Better - * Auth's implicit reads, INSERT defaults, and RETURNING. Its projection must - * already be deployed before the drop; the pending-drop audit cannot inspect - * queries generated inside the auth dependency. - */ - /** @deprecated No readers or writers; a departed member's ledger rows stay stamped to the org's period, so nothing needs capturing. */ - departedMemberUsage: decimal('departed_member_usage').notNull().default('0'), /** * Organization credit balance tracker. * @@ -1740,12 +1637,6 @@ export const organization = pgTable('organization', { updatedAt: timestamp('updated_at').defaultNow().notNull(), }) -/** - * Live columns of `organization` while the `departed_member_usage` drop is - * outstanding — see `userStatsColumns` for the pattern. - */ -export const organizationColumns = omit(getTableColumns(organization), ['departedMemberUsage']) - export const member = pgTable( 'member', { @@ -2267,9 +2158,7 @@ export const workspaceFiles = pgTable( */ displayName: text('display_name'), contentType: text('content_type').notNull(), - /** contract-pending(after the cutover and #7774 are fully deployed and size_bytes has no NULLs): drop size, workspace_files_sync_size_columns, and the temporary dev cutover runner — all application reads and writes use size_bytes */ - size: integer('size').notNull().default(0), - /** Exact byte size. The deploy migration backfills existing rows before this release serves traffic. */ + /** Exact byte size. */ sizeBytes: bigint('size_bytes', { mode: 'number' }), /** * Intrinsic pixel dimensions of an image file, captured lazily on first view (and stored so later @@ -2340,9 +2229,7 @@ export const workspaceFiles = pgTable( }) ) -/** Canonical application projection; the legacy `size` bridge is migration-only. */ -export const workspaceFileColumns = omit(getTableColumns(workspaceFiles), ['size']) -export type WorkspaceFileRow = Omit +export type WorkspaceFileRow = typeof workspaceFiles.$inferSelect export const workspaceFileSearchIndexStatusEnum = pgEnum('workspace_file_search_index_status', [ 'pending', diff --git a/packages/db/script-migrations-paused-billing-attribution.test.ts b/packages/db/script-migrations-paused-billing-attribution.test.ts index 38016621e23..6bdc8525e50 100644 --- a/packages/db/script-migrations-paused-billing-attribution.test.ts +++ b/packages/db/script-migrations-paused-billing-attribution.test.ts @@ -444,8 +444,6 @@ describe('script migration registry', () => { '0005_repair_unknown_table_row_provenance', '0006_repair_unknown_table_row_provenance_second_pass', '0007_repair_unknown_workspace_file_provenance', - '0008_backfill_workspace_file_size_bytes', - '0009_backfill_wel_residual_cost_total', '0010_backfill_credential_group_resource_policies', '0011_remap_legacy_knowledge_connector_credentials', '0012_reconcile_oauth_provider_lifecycle', diff --git a/packages/db/script-migrations/0003_backfill_workspace_storage_usage.ts b/packages/db/script-migrations/0003_backfill_workspace_storage_usage.ts index e26a7063768..aa36ff0194a 100644 --- a/packages/db/script-migrations/0003_backfill_workspace_storage_usage.ts +++ b/packages/db/script-migrations/0003_backfill_workspace_storage_usage.ts @@ -1,8 +1,4 @@ import type { Sql } from 'postgres' -import { - backfillWorkspaceFileSizeBytes, - createPostgresWorkspaceFileSizeBytesBackfillStore, -} from './0008_backfill_workspace_file_size_bytes' import type { ScriptMigration } from './types' export const WORKSPACE_STORAGE_RECONCILIATION_BATCH_SIZE = 250 @@ -251,7 +247,6 @@ export function createPostgresStorageReconciliationStore(sql: Sql): StorageRecon export const backfillWorkspaceStorageUsage: ScriptMigration = { name: '0003_backfill_workspace_storage_usage', async up(sql) { - await backfillWorkspaceFileSizeBytes(createPostgresWorkspaceFileSizeBytesBackfillStore(sql)) /** * Expand phase: seed only the additive workspace shadow ledger. Payer * aggregates remain under the old application's ownership until all old diff --git a/packages/db/script-migrations/0008_backfill_workspace_file_size_bytes.test.ts b/packages/db/script-migrations/0008_backfill_workspace_file_size_bytes.test.ts deleted file mode 100644 index 262f66be383..00000000000 --- a/packages/db/script-migrations/0008_backfill_workspace_file_size_bytes.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { - backfillWorkspaceFileSizeBytes, - WORKSPACE_FILE_SIZE_BYTES_BATCH_SIZE, - type WorkspaceFileSizeBytesBackfillStore, -} from './0008_backfill_workspace_file_size_bytes' - -describe('backfillWorkspaceFileSizeBytes', () => { - it('processes bounded keyset pages and counts rows changed', async () => { - const listCandidateIds = vi - .fn() - .mockResolvedValueOnce(['file-a', 'file-b']) - .mockResolvedValueOnce(['file-c']) - .mockResolvedValueOnce([]) - const backfillCandidateIds = vi - .fn() - .mockResolvedValueOnce(2) - .mockResolvedValueOnce(1) - - await expect( - backfillWorkspaceFileSizeBytes({ listCandidateIds, backfillCandidateIds }, { batchSize: 2 }) - ).resolves.toBe(3) - expect(listCandidateIds.mock.calls).toEqual([ - ['', 2], - ['file-b', 2], - ['file-c', 2], - ]) - expect(backfillCandidateIds.mock.calls).toEqual([[['file-a', 'file-b']], [['file-c']]]) - }) - - it('rejects non-advancing pages', async () => { - const store: WorkspaceFileSizeBytesBackfillStore = { - listCandidateIds: vi.fn().mockResolvedValue(['file-a']), - backfillCandidateIds: vi.fn().mockResolvedValue(1), - } - - await expect(backfillWorkspaceFileSizeBytes(store)).rejects.toThrow('non-advancing page') - }) - - it('treats database-ordered text cursors as opaque', async () => { - const listCandidateIds = vi - .fn() - .mockResolvedValueOnce(['lowercase-z']) - .mockResolvedValueOnce(['UPPERCASE-A']) - .mockResolvedValueOnce([]) - const backfillCandidateIds = vi - .fn() - .mockResolvedValue(1) - - await expect( - backfillWorkspaceFileSizeBytes({ listCandidateIds, backfillCandidateIds }) - ).resolves.toBe(2) - expect(listCandidateIds.mock.calls).toEqual([ - ['', WORKSPACE_FILE_SIZE_BYTES_BATCH_SIZE], - ['lowercase-z', WORKSPACE_FILE_SIZE_BYTES_BATCH_SIZE], - ['UPPERCASE-A', WORKSPACE_FILE_SIZE_BYTES_BATCH_SIZE], - ]) - }) -}) diff --git a/packages/db/script-migrations/0008_backfill_workspace_file_size_bytes.ts b/packages/db/script-migrations/0008_backfill_workspace_file_size_bytes.ts deleted file mode 100644 index 88e69b2e4bc..00000000000 --- a/packages/db/script-migrations/0008_backfill_workspace_file_size_bytes.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { createLogger } from '@sim/logger' -import type { Sql } from 'postgres' -import type { ScriptMigration } from './types' - -const logger = createLogger('WorkspaceFileSizeBytesBackfill') - -export const WORKSPACE_FILE_SIZE_BYTES_BATCH_SIZE = 1000 - -export interface WorkspaceFileSizeBytesBackfillStore { - /** Treats `afterId` as an opaque cursor ordered by the backing database's collation. */ - listCandidateIds(afterId: string, limit: number): Promise - backfillCandidateIds(ids: readonly string[]): Promise -} - -interface WorkspaceFileSizeBytesBackfillOptions { - batchSize?: number -} - -/** Backfills null size_bytes rows in bounded, independently committed keyset pages. */ -export async function backfillWorkspaceFileSizeBytes( - store: WorkspaceFileSizeBytesBackfillStore, - options: WorkspaceFileSizeBytesBackfillOptions = {} -): Promise { - const batchSize = options.batchSize ?? WORKSPACE_FILE_SIZE_BYTES_BATCH_SIZE - if (!Number.isInteger(batchSize) || batchSize <= 0) { - throw new Error('Workspace file size_bytes backfill batch size must be a positive integer') - } - - let afterId = '' - let backfilled = 0 - for (;;) { - const ids = await store.listCandidateIds(afterId, batchSize) - if (ids.length === 0) return backfilled - if (ids.length > batchSize) { - throw new Error('Workspace file size_bytes backfill store returned an oversized page') - } - const lastId = ids.at(-1) - if (!lastId || lastId === afterId) { - throw new Error('Workspace file size_bytes backfill store returned a non-advancing page') - } - backfilled += await store.backfillCandidateIds(ids) - afterId = lastId - } -} - -/** Creates the PostgreSQL store used by the deploy-time backfill. */ -export function createPostgresWorkspaceFileSizeBytesBackfillStore( - sql: Sql -): WorkspaceFileSizeBytesBackfillStore { - return { - async listCandidateIds(afterId, limit) { - const rows = await sql>` - SELECT id - FROM workspace_files - WHERE id > ${afterId} - AND size_bytes IS NULL - ORDER BY id - LIMIT ${limit} - ` - return rows.map((row) => row.id) - }, - - async backfillCandidateIds(ids) { - if (ids.length === 0) return 0 - return sql.begin(async (tx) => { - const rows = await tx>` - UPDATE workspace_files - SET size_bytes = size - WHERE id = ANY(${ids}::text[]) - AND size_bytes IS NULL - RETURNING id - ` - return rows.length - }) - }, - } -} - -export const backfillWorkspaceFileSizeBytesMigration: ScriptMigration = { - name: '0008_backfill_workspace_file_size_bytes', - async up(sql) { - const backfilled = await backfillWorkspaceFileSizeBytes( - createPostgresWorkspaceFileSizeBytesBackfillStore(sql) - ) - logger.info(`Workspace file size_bytes backfill complete: ${backfilled} file(s) updated.`) - }, -} diff --git a/packages/db/script-migrations/0009_backfill_wel_residual_cost_total.test.ts b/packages/db/script-migrations/0009_backfill_wel_residual_cost_total.test.ts deleted file mode 100644 index 48fbd52caab..00000000000 --- a/packages/db/script-migrations/0009_backfill_wel_residual_cost_total.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { - backfillWelResidualCostTotal, - WEL_RESIDUAL_COST_TOTAL_BATCH_SIZE, - type WelResidualCostTotalBackfillStore, -} from './0009_backfill_wel_residual_cost_total' - -describe('backfillWelResidualCostTotal', () => { - it('projects batches until the candidate set is empty and counts rows changed', async () => { - const projectBatch = vi - .fn() - .mockResolvedValueOnce(500) - .mockResolvedValueOnce(23) - .mockResolvedValueOnce(0) - - await expect(backfillWelResidualCostTotal({ projectBatch })).resolves.toBe(523) - expect(projectBatch.mock.calls).toEqual([ - [WEL_RESIDUAL_COST_TOTAL_BATCH_SIZE], - [WEL_RESIDUAL_COST_TOTAL_BATCH_SIZE], - [WEL_RESIDUAL_COST_TOTAL_BATCH_SIZE], - ]) - }) - - it('honors a custom batch size', async () => { - const projectBatch = vi - .fn() - .mockResolvedValueOnce(2) - .mockResolvedValueOnce(0) - - await expect(backfillWelResidualCostTotal({ projectBatch }, { batchSize: 2 })).resolves.toBe(2) - expect(projectBatch).toHaveBeenCalledWith(2) - }) - - it('rejects an invalid batch size', async () => { - const projectBatch = vi.fn() - - await expect(backfillWelResidualCostTotal({ projectBatch }, { batchSize: 0 })).rejects.toThrow( - 'positive integer' - ) - expect(projectBatch).not.toHaveBeenCalled() - }) - - it('fails loudly when the candidate set stops shrinking', async () => { - const projectBatch = vi - .fn() - .mockResolvedValue(1) - - await expect(backfillWelResidualCostTotal({ projectBatch })).rejects.toThrow('not shrinking') - }) -}) diff --git a/packages/db/script-migrations/0009_backfill_wel_residual_cost_total.ts b/packages/db/script-migrations/0009_backfill_wel_residual_cost_total.ts deleted file mode 100644 index 08b9d4e4800..00000000000 --- a/packages/db/script-migrations/0009_backfill_wel_residual_cost_total.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { createLogger } from '@sim/logger' -import type { Sql } from 'postgres' -import type { ScriptMigration } from './types' - -const logger = createLogger('WelResidualCostTotalBackfill') - -export const WEL_RESIDUAL_COST_TOTAL_BATCH_SIZE = 500 - -/** - * Safety valve for a store that keeps reporting progress: each batch must - * shrink the candidate set (projected rows no longer match `cost_total IS - * NULL`), so hitting this bound means the store is broken, not the data big. - */ -const MAX_BATCHES = 10_000 - -export interface WelResidualCostTotalBackfillStore { - /** Projects one bounded batch of candidates and reports rows changed. */ - projectBatch(limit: number): Promise -} - -interface WelResidualCostTotalBackfillOptions { - batchSize?: number -} - -/** - * Projects the residual `workflow_execution_logs.cost` json totals into - * `cost_total`/`models_used`, batch by batch, until no candidates remain. - */ -export async function backfillWelResidualCostTotal( - store: WelResidualCostTotalBackfillStore, - options: WelResidualCostTotalBackfillOptions = {} -): Promise { - const batchSize = options.batchSize ?? WEL_RESIDUAL_COST_TOTAL_BATCH_SIZE - if (!Number.isInteger(batchSize) || batchSize <= 0) { - throw new Error('Residual cost_total backfill batch size must be a positive integer') - } - - let projected = 0 - for (let batch = 0; batch < MAX_BATCHES; batch++) { - const changed = await store.projectBatch(batchSize) - if (changed === 0) return projected - projected += changed - } - throw new Error('Residual cost_total backfill did not converge; candidate set is not shrinking') -} - -/** - * Same candidate filter and projection as the 0220 procedure that introduced - * `cost_total`: a numeric `cost->>'total'` fills `cost_total`, and the - * `cost->'models'` keys fill `models_used`. Rows whose json lacks a numeric - * total have nothing to project and stay untouched. - */ -export function createPostgresWelResidualCostTotalBackfillStore( - sql: Sql -): WelResidualCostTotalBackfillStore { - return { - async projectBatch(limit) { - const result = await sql` - WITH candidates AS ( - SELECT id FROM workflow_execution_logs - WHERE cost_total IS NULL - AND cost ? 'total' - AND (cost->>'total') ~ '^-?[0-9]+(\\.[0-9]+)?$' - LIMIT ${limit} - ) - UPDATE workflow_execution_logs wel - SET cost_total = NULLIF(wel.cost->>'total', '')::numeric, - models_used = CASE - WHEN jsonb_typeof(wel.cost->'models') = 'object' - THEN ARRAY(SELECT jsonb_object_keys(wel.cost->'models')) - ELSE wel.models_used - END - FROM candidates - WHERE wel.id = candidates.id - ` - return result.count - }, - } -} - -/** - * The 0220 backfill projected every then-existing legacy `cost` json into - * `cost_total`; a transition-window writer path added a handful of rows after - * it ran with the json but no projection (verified on the prod replica - * 2026-08-26: ~23 of 4.77M rows carry a numeric total with `cost_total` NULL). - * This projects those stragglers so the pending `cost` DROP (see the - * contract-pending marker on the column) abandons nothing that `cost_total` - * should hold. The contract PR that drops `cost` must delete this entry from - * the registry in the same change — it reads the column. - */ -export const backfillWelResidualCostTotalMigration: ScriptMigration = { - name: '0009_backfill_wel_residual_cost_total', - async up(sql) { - const projected = await backfillWelResidualCostTotal( - createPostgresWelResidualCostTotalBackfillStore(sql) - ) - logger.info(`Residual cost_total backfill complete: ${projected} row(s) projected.`) - }, -} diff --git a/packages/db/script-migrations/0010_backfill_credential_group_resource_policies.test.ts b/packages/db/script-migrations/0010_backfill_credential_group_resource_policies.test.ts index f5644fd2b62..b412333b8d3 100644 --- a/packages/db/script-migrations/0010_backfill_credential_group_resource_policies.test.ts +++ b/packages/db/script-migrations/0010_backfill_credential_group_resource_policies.test.ts @@ -358,9 +358,9 @@ describe('Credential Group resource policy lifecycle', () => { expect(migration).toContain('CREATE TABLE "resource_policy"') expect(migration).not.toContain('credential_group_resource_policy_lifecycle') - expect(packageJson.scripts['db:push']).toContain( - 'scripts/reconcile-credential-group-resource-policies.ts' - ) + expect(packageJson.scripts['db:push']).toContain('scripts/push.ts') + const pushSource = await readFile(new URL('../scripts/push.ts', import.meta.url), 'utf8') + expect(pushSource).toContain('scripts/reconcile-credential-group-resource-policies.ts') expect(helperSource).not.toContain('LegacyResourcePolicy') expect(helperSource).not.toContain("document ? 'grants'") }) diff --git a/packages/db/script-migrations/index.ts b/packages/db/script-migrations/index.ts index 6a5d7e9db33..9a439ac6de4 100644 --- a/packages/db/script-migrations/index.ts +++ b/packages/db/script-migrations/index.ts @@ -10,8 +10,6 @@ import { backfillForkKnowledgeBaseFileOwnership } from './0004_backfill_fork_kb_ import { repairUnknownTableRowProvenance } from './0005_repair_unknown_table_row_provenance' import { repairUnknownTableRowProvenanceSecondPass } from './0006_repair_unknown_table_row_provenance_second_pass' import { repairUnknownWorkspaceFileProvenance } from './0007_repair_unknown_workspace_file_provenance' -import { backfillWorkspaceFileSizeBytesMigration } from './0008_backfill_workspace_file_size_bytes' -import { backfillWelResidualCostTotalMigration } from './0009_backfill_wel_residual_cost_total' import { backfillCredentialGroupResourcePolicies } from './0010_backfill_credential_group_resource_policies' import { remapLegacyKnowledgeConnectorCredentialsMigration } from './0011_remap_legacy_knowledge_connector_credentials' import type { ScriptMigration } from './types' @@ -31,8 +29,6 @@ export const scriptMigrations: readonly ScriptMigration[] = [ repairUnknownTableRowProvenance, repairUnknownTableRowProvenanceSecondPass, repairUnknownWorkspaceFileProvenance, - backfillWorkspaceFileSizeBytesMigration, - backfillWelResidualCostTotalMigration, backfillCredentialGroupResourcePolicies, remapLegacyKnowledgeConnectorCredentialsMigration, reconcileOAuthProviderLifecycleMigration, diff --git a/packages/db/scripts/apply-dev-workspace-file-size-cutover.ts b/packages/db/scripts/apply-dev-workspace-file-size-cutover.ts deleted file mode 100644 index de330b834f3..00000000000 --- a/packages/db/scripts/apply-dev-workspace-file-size-cutover.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createLogger } from '@sim/logger' -import postgres from 'postgres' -import { - backfillWorkspaceFileSizeBytes, - createPostgresWorkspaceFileSizeBytesBackfillStore, -} from '../script-migrations/0008_backfill_workspace_file_size_bytes' - -const logger = createLogger('DevWorkspaceFileSizeCutover') -const MIGRATION_NAME = '0008_backfill_workspace_file_size_bytes' -const MIGRATION_SQL_URL = new URL( - '../migrations/0308_workspace_file_size_cutover.sql', - import.meta.url -) -const url = process.env.MIGRATION_DATABASE_URL || process.env.DATABASE_URL - -if (!url) { - throw new Error('Missing MIGRATION_DATABASE_URL or DATABASE_URL') -} - -const sql = postgres(url, { - max: 1, - connect_timeout: 10, - max_lifetime: null, - connection: { application_name: 'sim-dev-workspace-file-size-cutover' }, -}) - -try { - const migrationSql = await Bun.file(MIGRATION_SQL_URL).text() - await sql.unsafe(migrationSql) - const backfilled = await backfillWorkspaceFileSizeBytes( - createPostgresWorkspaceFileSizeBytesBackfillStore(sql) - ) - await sql` - CREATE TABLE IF NOT EXISTS script_migrations ( - name text PRIMARY KEY, - applied_at timestamptz NOT NULL DEFAULT now() - ) - ` - await sql` - INSERT INTO script_migrations (name) VALUES (${MIGRATION_NAME}) - ON CONFLICT (name) DO NOTHING - ` - logger.info('Dev workspace file size cutover completed', { backfilled }) -} finally { - await sql.end() -} diff --git a/packages/db/scripts/push.ts b/packages/db/scripts/push.ts new file mode 100644 index 00000000000..08b93f9557e --- /dev/null +++ b/packages/db/scripts/push.ts @@ -0,0 +1,13 @@ +/** Forward push flags to Drizzle before running the schema reconciliation steps. */ +const commands = [ + ['bunx', 'drizzle-kit', 'push', '--config=./drizzle.config.ts', ...process.argv.slice(2)], + ['bun', '--env-file=.env', 'run', './scripts/reconcile-credential-group-resource-policies.ts'], + ['bun', '--env-file=.env', 'run', './scripts/reconcile-oauth-provider.ts'], + ['bun', '--env-file=.env', 'run', './script-migrations/0016_backfill_search_vectors.ts'], +] + +for (const command of commands) { + const child = Bun.spawn(command, { stdin: 'inherit', stdout: 'inherit', stderr: 'inherit' }) + const exitCode = await child.exited + if (exitCode !== 0) process.exit(exitCode) +} diff --git a/packages/db/scripts/retired-columns.postgres.test.ts b/packages/db/scripts/retired-columns.postgres.test.ts new file mode 100644 index 00000000000..85961b7dd61 --- /dev/null +++ b/packages/db/scripts/retired-columns.postgres.test.ts @@ -0,0 +1,131 @@ +import { readFileSync } from 'node:fs' +import { generateId } from '@sim/utils/id' +import postgres from 'postgres' +import { describe, expect, it } from 'vitest' + +const databaseUrl = process.env.RETIRED_COLUMNS_TEST_DATABASE_URL +const migration = readFileSync( + new URL('../migrations/0348_drop_retired_usage_columns.sql', import.meta.url), + 'utf8' +) +const bridge = readFileSync( + new URL('../migrations/0308_workspace_file_size_cutover.sql', import.meta.url), + 'utf8' +) +const retiredStats = [ + 'total_manual_executions', + 'total_api_calls', + 'total_webhook_triggers', + 'total_scheduled_executions', + 'total_chat_executions', + 'total_mcp_executions', + 'total_tokens_used', + 'total_cost', + 'current_period_cost', + 'pro_period_cost_snapshot', + 'pro_period_cost_snapshot_at', + 'total_copilot_cost', + 'current_period_copilot_cost', + 'total_copilot_tokens', + 'total_copilot_calls', + 'total_mcp_copilot_calls', + 'total_mcp_copilot_cost', + 'current_period_mcp_copilot_cost', + 'last_active', +] +const receipts = [ + '0008_backfill_workspace_file_size_bytes', + '0009_backfill_wel_residual_cost_total', +] + +async function fixture(run: (sql: postgres.Sql) => Promise): Promise { + const sql = postgres(databaseUrl!, { max: 1, onnotice: () => {} }) + const schemaName = `retired_columns_${generateId().replaceAll('-', '')}` + try { + await sql`CREATE SCHEMA ${sql(schemaName)}` + await sql`SET search_path = ${sql(schemaName)}` + await sql`CREATE TABLE script_migrations (name text PRIMARY KEY)` + await sql`CREATE TABLE organization (id text, departed_member_usage numeric, credit_balance numeric)` + await sql`CREATE TABLE user_stats (id text, credit_balance numeric)` + for (const column of retiredStats) { + await sql`ALTER TABLE user_stats ADD COLUMN ${sql(column)} text` + } + await sql`CREATE TABLE workflow_execution_logs (id text, cost jsonb, cost_total numeric)` + await sql`CREATE TABLE workspace_files (id text, size integer NOT NULL, size_bytes bigint)` + await sql.unsafe(bridge) + await run(sql) + } finally { + try { + await sql`DROP SCHEMA IF EXISTS ${sql(schemaName)} CASCADE` + } finally { + await sql.end() + } + } +} + +async function apply(sql: postgres.Sql): Promise { + for (const statement of migration.split('--> statement-breakpoint')) { + await sql.unsafe(statement) + } +} + +describe.skipIf(!databaseUrl)('retired-column contract migration', () => { + it('allows a fresh database and replays after the columns are gone', async () => { + await fixture(async (sql) => { + await apply(sql) + await apply(sql) + await sql`INSERT INTO workspace_files (id, size_bytes) VALUES ('new-file', 5000000000)` + expect(await sql`SELECT size_bytes::text AS size FROM workspace_files`).toEqual([ + { size: '5000000000' }, + ]) + }) + }) + + it.each(receipts)( + 'blocks a populated database missing %s before dropping anything', + async (missing) => { + await fixture(async (sql) => { + for (const receipt of receipts.filter((name) => name !== missing)) { + await sql`INSERT INTO script_migrations (name) VALUES (${receipt})` + } + await sql`INSERT INTO workspace_files (id, size_bytes) VALUES ('file', 5000000000)` + await sql`INSERT INTO workflow_execution_logs (id, cost, cost_total) VALUES ('log', '{"total": 1.25}', 1.25)` + await expect(apply(sql)).rejects.toThrow(missing) + expect(await sql`SELECT size FROM workspace_files`).toEqual([{ size: 2147483647 }]) + expect(await sql`SELECT cost FROM workflow_execution_logs`).toEqual([ + { cost: { total: 1.25 } }, + ]) + }) + } + ) + + it('preserves canonical values and removes all retired columns and the bridge', async () => { + await fixture(async (sql) => { + for (const receipt of receipts) { + await sql`INSERT INTO script_migrations (name) VALUES (${receipt})` + } + await sql`INSERT INTO workspace_files (id, size_bytes) VALUES ('file', 5000000000)` + await sql`INSERT INTO workflow_execution_logs (id, cost, cost_total) VALUES ('log', '{"total": 1.25}', 1.25)` + await sql`INSERT INTO user_stats (id, credit_balance) VALUES ('user', 12.50)` + await sql`INSERT INTO organization (id, credit_balance) VALUES ('org', 25.75)` + await apply(sql) + await apply(sql) + expect(await sql`SELECT * FROM workspace_files`).toEqual([ + { id: 'file', size_bytes: '5000000000' }, + ]) + expect(await sql`SELECT * FROM workflow_execution_logs`).toEqual([ + { id: 'log', cost_total: '1.25' }, + ]) + expect(await sql`SELECT * FROM user_stats`).toEqual([{ id: 'user', credit_balance: '12.50' }]) + expect(await sql`SELECT * FROM organization`).toEqual([ + { id: 'org', credit_balance: '25.75' }, + ]) + expect( + await sql`SELECT 1 FROM pg_trigger WHERE tgrelid = 'workspace_files'::regclass AND tgname = 'workspace_files_sync_size_columns'` + ).toEqual([]) + expect( + await sql`SELECT to_regprocedure('sync_workspace_file_size_columns()') AS bridge` + ).toEqual([{ bridge: null }]) + }) + }) +}) diff --git a/packages/db/workspace-files-schema.test.ts b/packages/db/workspace-files-schema.test.ts deleted file mode 100644 index 66436d675db..00000000000 --- a/packages/db/workspace-files-schema.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { workspaceFileColumns } from './schema' - -describe('workspaceFileColumns', () => { - it('excludes the legacy size bridge from application projections', () => { - expect(workspaceFileColumns).toHaveProperty('sizeBytes') - expect(workspaceFileColumns).not.toHaveProperty('size') - }) -}) diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts index f51da5b0377..f8da60c8e58 100644 --- a/packages/testing/src/mocks/schema.mock.ts +++ b/packages/testing/src/mocks/schema.mock.ts @@ -1,5 +1,3 @@ -import { omit } from '@sim/utils/object' - /** * Comprehensive mock for `@sim/db/schema`. * @@ -55,7 +53,6 @@ const workflowExecutionLogsMock = { endedAt: 'workflowExecutionLogs.endedAt', totalDurationMs: 'workflowExecutionLogs.totalDurationMs', executionData: 'workflowExecutionLogs.executionData', - cost: 'workflowExecutionLogs.cost', costTotal: 'workflowExecutionLogs.costTotal', modelsUsed: 'workflowExecutionLogs.modelsUsed', files: 'workflowExecutionLogs.files', @@ -65,32 +62,13 @@ const workflowExecutionLogsMock = { const userStatsMock = { id: 'userStats.id', userId: 'userStats.userId', - totalManualExecutions: 'userStats.totalManualExecutions', - totalApiCalls: 'userStats.totalApiCalls', - totalWebhookTriggers: 'userStats.totalWebhookTriggers', - totalScheduledExecutions: 'userStats.totalScheduledExecutions', - totalChatExecutions: 'userStats.totalChatExecutions', - totalMcpExecutions: 'userStats.totalMcpExecutions', - totalTokensUsed: 'userStats.totalTokensUsed', - totalCost: 'userStats.totalCost', currentUsageLimit: 'userStats.currentUsageLimit', usageLimitUpdatedAt: 'userStats.usageLimitUpdatedAt', - currentPeriodCost: 'userStats.currentPeriodCost', lastPeriodCost: 'userStats.lastPeriodCost', billedOverageThisPeriod: 'userStats.billedOverageThisPeriod', - proPeriodCostSnapshot: 'userStats.proPeriodCostSnapshot', - proPeriodCostSnapshotAt: 'userStats.proPeriodCostSnapshotAt', creditBalance: 'userStats.creditBalance', - totalCopilotCost: 'userStats.totalCopilotCost', - currentPeriodCopilotCost: 'userStats.currentPeriodCopilotCost', lastPeriodCopilotCost: 'userStats.lastPeriodCopilotCost', - totalCopilotTokens: 'userStats.totalCopilotTokens', - totalCopilotCalls: 'userStats.totalCopilotCalls', - totalMcpCopilotCalls: 'userStats.totalMcpCopilotCalls', - totalMcpCopilotCost: 'userStats.totalMcpCopilotCost', - currentPeriodMcpCopilotCost: 'userStats.currentPeriodMcpCopilotCost', storageUsedBytes: 'userStats.storageUsedBytes', - lastActive: 'userStats.lastActive', billingBlocked: 'userStats.billingBlocked', billingBlockedReason: 'userStats.billingBlockedReason', limitNotifications: 'userStats.limitNotifications', @@ -105,7 +83,6 @@ const organizationMock = { whitelabelSettings: 'organization.whitelabelSettings', orgUsageLimit: 'organization.orgUsageLimit', storageUsedBytes: 'organization.storageUsedBytes', - departedMemberUsage: 'organization.departedMemberUsage', sessionPolicySettings: 'organization.sessionPolicySettings', securityPolicyVersion: 'organization.securityPolicyVersion', dataRetentionSettings: 'organization.dataRetentionSettings', @@ -322,7 +299,6 @@ export const schemaMock = { updatedAt: 'secretUsage.updatedAt', }, workflowExecutionLogs: workflowExecutionLogsMock, - workflowExecutionLogColumns: omit(workflowExecutionLogsMock, ['cost']), executionLargeValues: { key: 'executionLargeValues.key', workspaceId: 'executionLargeValues.workspaceId', @@ -549,27 +525,6 @@ export const schemaMock = { }, billingBlockedReasonEnum: 'billingBlockedReasonEnum', userStats: userStatsMock, - userStatsColumns: omit(userStatsMock, [ - 'totalManualExecutions', - 'totalApiCalls', - 'totalWebhookTriggers', - 'totalScheduledExecutions', - 'totalChatExecutions', - 'totalMcpExecutions', - 'totalTokensUsed', - 'totalCost', - 'currentPeriodCost', - 'proPeriodCostSnapshot', - 'proPeriodCostSnapshotAt', - 'totalCopilotCost', - 'currentPeriodCopilotCost', - 'totalCopilotTokens', - 'totalCopilotCalls', - 'totalMcpCopilotCalls', - 'totalMcpCopilotCost', - 'currentPeriodMcpCopilotCost', - 'lastActive', - ]), customBlock: { id: 'customBlock.id', organizationId: 'customBlock.organizationId', @@ -657,7 +612,6 @@ export const schemaMock = { updatedAt: 'chat.updatedAt', }, organization: organizationMock, - organizationColumns: omit(organizationMock, ['departedMemberUsage']), member: { id: 'member.id', userId: 'member.userId', @@ -740,7 +694,6 @@ export const schemaMock = { workspaceFiles: workspaceFilesMock, workspaceFileSearchIndex: workspaceFileSearchIndexMock, workspaceFileSearchSegment: workspaceFileSearchSegmentMock, - workspaceFileColumns: workspaceFilesMock, workspaceFileSecretProvenance: { fileId: 'workspaceFileSecretProvenance.fileId', contentUpdatedAt: 'workspaceFileSecretProvenance.contentUpdatedAt', diff --git a/scripts/check-pending-drop-tables.test.ts b/scripts/check-pending-drop-tables.test.ts index dbc9f8aa79b..121ffc41bd9 100644 --- a/scripts/check-pending-drop-tables.test.ts +++ b/scripts/check-pending-drop-tables.test.ts @@ -1,157 +1,82 @@ -import { auditFile } from '@scripts/check-pending-drop-tables' +import { auditFile, mayReferencePendingTable } from '@scripts/check-pending-drop-tables' import { describe, expect, it } from 'vitest' -const tables = new Map([ - ['userStats', new Set(['totalCost'])], - ['organization', new Set(['departedMemberUsage'])], -]) -const columns = new Map([ - ['userStatsColumns', 'userStats'], - ['organizationColumns', 'organization'], -]) +const tables = new Map([['retiringTable', new Set(['retired', 'otherRetired'])]]) -function audit(source: string) { - return auditFile('insert-example.ts', source, tables, columns) +function audit(statement: string) { + return auditFile( + 'query-example.ts', + `import { retiringTable } from '@sim/db/schema'; ${statement}`, + tables + ) } -describe('pending-drop INSERT audit', () => { +describe('pending-drop query audit', () => { it.each([ - 'db.insert(userStats).values({ id: "id", userId: "user" })', - 'db.insert(userStats).values({ id: "id" }).onConflictDoNothing().returning({ id: userStats.id })', - 'const target = userStats; tx.insert(target).values({ id: "id" })', - 'db.insert(alias(userStats, "stats")).values({ id: "id" })', + 'db.insert(retiringTable).values({ id: "id" })', + 'db.insert(retiringTable).values({ id: "id" }).onConflictDoNothing().returning({ id: retiringTable.id })', + 'const target = retiringTable; tx.insert(target).values({ id: "id" })', + 'db.insert(alias(retiringTable, "stats")).values({ id: "id" })', ])('rejects implicit DEFAULT columns: %s', (statement) => { - const findings = audit(`import { userStats } from '@sim/db/schema'; ${statement}`) - expect(findings.some((finding) => finding.pattern.startsWith('insert()'))).toBe(true) + expect(audit(statement)).toEqual([ + expect.objectContaining({ pattern: expect.stringContaining('insert()') }), + ]) }) it.each([ - "import { userStats as stats } from '@sim/db/schema'; db.insert(stats).values({})", - "import * as schema from '@sim/db/schema'; db.insert(schema.userStats).values({})", + "import { retiringTable as stats } from '@sim/db/schema'; db.insert(stats).values({})", + "import * as schema from '@sim/db/schema'; db.insert(schema.retiringTable).values({})", + "import { retiringTable as stats } from '@sim/db/schema'; db.select().from(alias(stats, 's'))", + "import * as schema from '@sim/db/schema'; db.select().from(schema.retiringTable)", ])('resolves renamed and namespace table imports', (source) => { - expect(audit(source)).toHaveLength(1) + expect(auditFile('query-example.ts', source, tables)).toHaveLength(1) }) it.each([ - `import { userStats, userStatsColumns } from '@sim/db/schema'; - import { withInsertColumns } from '@sim/db/insert-columns'; - db.insert(withInsertColumns(userStats, userStatsColumns)).values({}).returning()`, - `import { userStats as stats, userStatsColumns as live } from '@sim/db/schema'; - import { withInsertColumns as project } from '@sim/db/insert-columns'; - db.insert(project(stats, live)).values({})`, - `import * as schema from '@sim/db/schema'; - import { withInsertColumns } from '@sim/db/insert-columns'; - db.insert(withInsertColumns(schema.userStats, schema.userStatsColumns)).values({})`, - ])('accepts the validated live-column map', (source) => { - expect(audit(source)).toEqual([]) - }) - - it.each(['{}', 'organizationColumns', '{ ...userStatsColumns, totalCost: userStats.totalCost }'])( - 'rejects an unverified or mismatched selection: %s', - (selection) => { - expect( - audit(` - import { userStats, userStatsColumns, organizationColumns } from '@sim/db/schema'; - import { withInsertColumns } from '@sim/db/insert-columns'; - db.insert(withInsertColumns(userStats, ${selection})).values({}); - `) - ).toHaveLength(1) - } - ) - - it('still rejects broad reads', () => { - expect( - audit(`import { userStats } from '@sim/db/schema'; db.select().from(userStats)`) - ).toHaveLength(1) + 'db.select().from(retiringTable)', + 'db.selectDistinct().from(retiringTable)', + 'db.selectDistinctOn([retiringTable.id]).from(retiringTable)', + 'db.query.retiringTable.findFirst()', + 'db.query.retiringTable.findMany({ where: predicate })', + 'db.update(retiringTable).set({ id: "id" }).returning()', + 'db.delete(retiringTable).returning()', + 'getTableColumns(retiringTable)', + ])('rejects implicit full-table selections: %s', (statement) => { + expect(audit(statement)).toHaveLength(1) }) it.each([ - 'function write(userStatsColumns) { INSERT }', - 'const write = ({ userStatsColumns }) => { INSERT }', - 'function write(userStatsColumns = arbitrary) { INSERT }', - '{ const userStatsColumns = arbitrary; INSERT }', - 'function write() { INSERT; var userStatsColumns = arbitrary }', - 'try {} catch (userStatsColumns) { INSERT }', - 'for (const userStatsColumns of selections) { INSERT }', - 'const write = function userStatsColumns() { INSERT }', - ])('rejects a shadowed live-column map: %s', (scope) => { - const source = scope.replace( - 'INSERT', - 'db.insert(withInsertColumns(userStats, userStatsColumns)).values({});' - ) - expect( - audit(` - import { userStats, userStatsColumns } from '@sim/db/schema'; - import { withInsertColumns } from '@sim/db/insert-columns'; - ${source} - `) - ).toEqual([ - expect.objectContaining({ pattern: expect.stringContaining('validated live-column map') }), - ]) + 'db.select({ id: retiringTable.id }).from(retiringTable)', + 'db.query.retiringTable.findFirst({ columns: { id: true } })', + 'db.update(retiringTable).set({ id: "id" }).returning({ id: retiringTable.id })', + 'omit(getTableColumns(retiringTable), ["retired", "otherRetired"])', + 'const { retired, otherRetired, ...live } = getTableColumns(retiringTable)', + 'db.insert(activeTable).values({ id: "id" }).returning()', + ])('allows explicit selections and tables without pending drops: %s', (statement) => { + expect(audit(statement)).toEqual([]) }) it.each([ - `import { userStats, userStatsColumns as live } from '@sim/db/schema'; - function write(live) { db.insert(withInsertColumns(userStats, live)).values({}) }`, - `import { userStats } from '@sim/db/schema'; - import * as schema from '@sim/db/schema'; - function write(schema) { - db.insert(withInsertColumns(userStats, schema.userStatsColumns)).values({}) - }`, - ])('rejects shadowed renamed and namespace selections', (source) => { - expect(audit(`import { withInsertColumns } from '@sim/db/insert-columns'; ${source}`)).toEqual([ - expect.objectContaining({ pattern: expect.stringContaining('validated live-column map') }), + 'omit(getTableColumns(retiringTable), ["retired"])', + 'const { retired, ...live } = getTableColumns(retiringTable)', + ])('rejects an incomplete live-column selection: %s', (statement) => { + expect(audit(statement)).toEqual([ + expect.objectContaining({ pattern: expect.stringContaining('otherRetired') }), ]) }) - it.each([ - `import { withInsertColumns } from '@sim/db/insert-columns'; - function write(withInsertColumns) { - db.insert(withInsertColumns(userStats, userStatsColumns)).values({}) - }`, - `import * as inserts from '@sim/db/insert-columns'; - function write(inserts) { - db.insert(inserts.withInsertColumns(userStats, userStatsColumns)).values({}) - }`, - ])('rejects a shadowed INSERT helper', (source) => { + it('scans escaped identifiers without treating comments as table references', () => { expect( - audit(`import { userStats, userStatsColumns } from '@sim/db/schema'; ${source}`) - ).toEqual([ - expect.objectContaining({ pattern: expect.stringContaining('imported INSERT helper') }), - ]) - }) - - it('keeps imports valid outside the shadowing scope', () => { - expect( - audit(` - import { userStats, userStatsColumns } from '@sim/db/schema'; - import { withInsertColumns } from '@sim/db/insert-columns'; - function unrelated(userStatsColumns, withInsertColumns) {} - { const userStatsColumns = arbitrary } - function write() { - db.insert(withInsertColumns(userStats, userStatsColumns)).values({}) - } - `) - ).toEqual([]) - }) - - it('accepts namespace-imported INSERT helpers', () => { - expect( - audit(` - import * as schema from '@sim/db/schema'; - import * as inserts from '@sim/db/insert-columns'; - db.insert(inserts.withInsertColumns(schema.userStats, schema.userStatsColumns)).values({}); - `) - ).toEqual([]) - }) - - it('validates namespace-imported insert helpers', () => { + mayReferencePendingTable( + "import { retiring\\u0054able } from '@sim/db/schema'", + new Set(tables.keys()) + ) + ).toBe(true) expect( - audit(` - import { userStats } from '@sim/db/schema'; - import * as inserts from '@sim/db/insert-columns'; - db.insert(inserts.withInsertColumns(userStats, {})).values({}); - `) - ).toHaveLength(1) + mayReferencePendingTable( + "import { activeTable } from '@sim/db/schema'; // retiringTable", + new Set(tables.keys()) + ) + ).toBe(false) }) }) diff --git a/scripts/check-pending-drop-tables.ts b/scripts/check-pending-drop-tables.ts index 030a1a15b89..894c2e664f9 100644 --- a/scripts/check-pending-drop-tables.ts +++ b/scripts/check-pending-drop-tables.ts @@ -10,8 +10,8 @@ * single argless read puts the doomed columns back into live SQL and would fail with * 42703 against the already-migrated database for the whole cutover window of the * contract deploy. Reads of these tables must name the columns they want. - * INSERTs must use withInsertColumns(table, liveColumns): omitted values still - * generate named DEFAULT columns. The supplied map must be a validated schema export. + * INSERTs also name omitted columns with DEFAULT values. Remove retired columns + * from the application table definition before the contract deploy. * * The audit derives everything from schema.ts itself and retires when the contract PR * deletes the markers: @@ -246,110 +246,6 @@ function objectKeys(node: unknown): Set | null { interface TableBindings { locals: Map namespaces: Set - insertHelpers: Set - insertHelperNamespaces: Set -} - -/** Live selections already validated against the pending column declarations. */ -function readLiveColumnMaps(pendingTables: Map>): Map { - const { program } = parseSource(SCHEMA_PATH, readFileSync(SCHEMA_PATH, 'utf8')) - const selections = new Map() - const visit = (node: SyntaxNode) => { - if (node.type === 'VariableDeclarator') { - const name = propertyName(node.id) - const init = unwrap(node.init) - if (name && init?.type === 'CallExpression' && identifierName(init.callee) === 'omit') { - const columns = unwrap(Array.isArray(init.arguments) ? init.arguments[0] : undefined) - if ( - columns?.type === 'CallExpression' && - identifierName(columns.callee) === 'getTableColumns' - ) { - const table = identifierName( - Array.isArray(columns.arguments) ? columns.arguments[0] : undefined - ) - const doomed = table ? pendingTables.get(table) : undefined - if (table && doomed && sanctionedOmitMissing(init, doomed)?.length === 0) { - selections.set(name, table) - } - } - } - } - for (const child of getChildNodes(node)) visit(child) - } - visit(program) - return selections -} - -interface ImportBinding { - module: string - name: string -} - -/** - * Binds references within this file, without loading dependencies or standard - * libraries. Initialize lazily: only INSERT helpers need trusted import provenance. - * TypeScript resolves parameters, block locals, destructuring and hoisted bindings - * so a shadowed import cannot authorize an arbitrary column map or helper. - */ -function createImportResolver(file: string, source: string) { - let checker: ts.TypeChecker | undefined - const identifiers = new Map() - const resolveIdentifier = (node: SyntaxNode): ImportBinding | null => { - if (!checker) { - const filename = resolve(file) - const sourceFile = ts.createSourceFile(filename, source, ts.ScriptTarget.Latest, true) - const host: ts.CompilerHost = { - getSourceFile: (name) => (name === filename ? sourceFile : undefined), - getDefaultLibFileName: () => 'lib.d.ts', - writeFile: () => {}, - getCurrentDirectory: () => dirname(filename), - getDirectories: () => [], - fileExists: (name) => name === filename, - readFile: (name) => (name === filename ? source : undefined), - getCanonicalFileName: (name) => name, - useCaseSensitiveFileNames: () => true, - getNewLine: () => '\n', - } - checker = ts - .createProgram([filename], { noLib: true, noResolve: true }, host) - .getTypeChecker() - const index = (child: ts.Node) => { - if (ts.isIdentifier(child)) identifiers.set(child.getStart(sourceFile), child) - ts.forEachChild(child, index) - } - index(sourceFile) - } - const identifier = typeof node.start === 'number' ? identifiers.get(node.start) : undefined - const declarations = identifier - ? checker.getSymbolAtLocation(identifier)?.declarations - : undefined - if (declarations?.length !== 1) return null - const declaration = declarations[0] - if (!ts.isImportSpecifier(declaration) && !ts.isNamespaceImport(declaration)) return null - let parent: ts.Node = declaration.parent - while (!ts.isImportDeclaration(parent)) { - if (!parent.parent) return null - parent = parent.parent - } - if (!ts.isStringLiteral(parent.moduleSpecifier)) return null - return { - module: parent.moduleSpecifier.text, - name: ts.isImportSpecifier(declaration) - ? (declaration.propertyName ?? declaration.name).text - : '*', - } - } - - return (node: unknown): ImportBinding | null => { - const expression = unwrap(node) - if (expression?.type === 'Identifier') return resolveIdentifier(expression) - if (expression?.type !== 'MemberExpression' || expression.computed) return null - const object = unwrap(expression.object) - if (object?.type !== 'Identifier') return null - const binding = resolveIdentifier(object) - const member = propertyName(expression.property) - return binding?.name === '*' && member ? { module: binding.module, name: member } : null - } } /** @@ -408,8 +304,6 @@ function collectTableBindings( const bindings: TableBindings = { locals: new Map(), namespaces: new Set(), - insertHelpers: new Set(), - insertHelperNamespaces: new Set(), } const visitImports = (node: SyntaxNode) => { @@ -421,20 +315,10 @@ function collectTableBindings( if (!local) continue if (specifier.type === 'ImportNamespaceSpecifier') { bindings.namespaces.add(local) - if (isSyntaxNode(node.source) && node.source.value === '@sim/db/insert-columns') { - bindings.insertHelperNamespaces.add(local) - } continue } if (specifier.type !== 'ImportSpecifier') continue const imported = propertyName(specifier.imported) - if ( - imported === 'withInsertColumns' && - isSyntaxNode(node.source) && - node.source.value === '@sim/db/insert-columns' - ) { - bindings.insertHelpers.add(local) - } if (imported && imported !== local && pendingTables.has(imported)) { bindings.locals.set(local, imported) } @@ -476,7 +360,7 @@ function collectTableBindings( /** * Validates the sanctioned live-column builders around a `getTableColumns(t)` * call: `omit(getTableColumns(t), ['doomed', ...])` (the `Columns` - * helpers in schema.ts, e.g. `workspaceFileColumns`) and + * helpers in schema.ts) and * `const { doomed, ...live } = getTableColumns(t)`. Returns `null` when the * surrounding form is not a sanctioned builder at all, otherwise the doomed * columns the builder fails to name away — `[]` means fully sanctioned. Any @@ -522,37 +406,12 @@ function checkCall( parent: SyntaxNode | null, pendingTables: Map>, bindings: TableBindings, - liveColumnMaps: Map, - resolveImport: ReturnType, report: (node: SyntaxNode, table: string, pattern: string) => void ): void { const callee = isSyntaxNode(call.callee) ? call.callee : null const args = Array.isArray(call.arguments) ? call.arguments : [] const resolveArg = (node: unknown) => resolveTable(node, pendingTables, bindings) - const isInsertHelper = - bindings.insertHelpers.has(identifierName(callee) ?? '') || - (callee?.type === 'MemberExpression' && - !callee.computed && - propertyName(callee.property) === 'withInsertColumns' && - bindings.insertHelperNamespaces.has(identifierName(callee.object) ?? '')) - if (isInsertHelper) { - const table = resolveArg(args[0]) - if (!table) return - const helper = resolveImport(callee) - const columns = resolveImport(args[1]) - if (helper?.module !== '@sim/db/insert-columns' || helper.name !== 'withInsertColumns') { - report(call, table, 'withInsertColumns() must resolve to the imported INSERT helper') - } else if ( - !columns || - !isSchemaModule(columns.module) || - liveColumnMaps.get(columns.name) !== table - ) { - report(call, table, "withInsertColumns() must use this table's validated live-column map") - } - return - } - // getTableColumns(pendingTable) — spreads every declared column unless the // doomed ones are verifiably named away on the spot. if (identifierName(callee) === 'getTableColumns') { @@ -573,11 +432,7 @@ function checkCall( if (method === 'insert') { const table = resolveArg(args[0]) if (table) { - report( - call, - table, - 'insert() names every declared column, including omitted DEFAULT values; use withInsertColumns()' - ) + report(call, table, 'insert() names every declared column, including omitted DEFAULT values') } return } @@ -649,8 +504,7 @@ function checkCall( export function auditFile( file: string, source: string, - pendingTables: Map>, - liveColumnMaps: Map + pendingTables: Map> ): Violation[] { const violations: Violation[] = [] let program: SyntaxNode @@ -667,7 +521,6 @@ export function auditFile( } const bindings = collectTableBindings(program, pendingTables) - const resolveImport = createImportResolver(file, source) const report = (node: SyntaxNode, table: string, pattern: string) => { violations.push({ file, line: node.loc?.start.line ?? 1, table, pattern }) @@ -675,7 +528,7 @@ export function auditFile( const visit = (node: SyntaxNode, parent: SyntaxNode | null) => { if (node.type === 'CallExpression') { - checkCall(node, parent, pendingTables, bindings, liveColumnMaps, resolveImport, report) + checkCall(node, parent, pendingTables, bindings, report) } for (const child of getChildNodes(node)) visit(child, node) } @@ -726,13 +579,12 @@ function main(): void { // must keep naming every doomed column away, including ones deprecated later. const skipFiles = new Set([fileURLToPath(import.meta.url)]) const pendingTableNames = new Set(pendingTables.keys()) - const liveColumnMaps = readLiveColumnMaps(pendingTables) const violations: Violation[] = [] for (const file of SCAN_DIRS.flatMap((dir) => collectSources(dir))) { if (skipFiles.has(file) || /\.test\.(ts|tsx|mts|cts)$/.test(file)) continue const source = readFileSync(file, 'utf8') if (file !== SCHEMA_PATH && !mayReferencePendingTable(source, pendingTableNames)) continue - violations.push(...auditFile(file, source, pendingTables, liveColumnMaps)) + violations.push(...auditFile(file, source, pendingTables)) } if (violations.length === 0) { @@ -746,7 +598,7 @@ function main(): void { `❌ Found ${violations.length} unsafe read(s) or insert(s) of pending-drop tables.\n` + 'These tables carry a `contract-pending` marker in packages/db/schema.ts: deprecated\n' + 'columns are awaiting DROP, and full-table reads or inserts re-introduce them into live SQL\n' + - 'and 42703 during the contract deploy. Use the validated live-column maps.\n' + 'and 42703 during the contract deploy. Exclude retired columns from generated SQL.\n' ) for (const violation of violations) { console.error( From 0bef03fbe402eacddb5c29e9d364ae39ade29641 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 15 Sep 2026 18:57:29 -0700 Subject: [PATCH 02/43] fix(browser): improve snapshots, native controls and action reliability (#7872) * fix(browser): preserve click targets and bound screenshot capture * fix(browser): preserve observations and support native form controls * fix(browser): share snapshot text budget with inline fragments --- apps/desktop/e2e/browser-tools.spec.ts | 276 ++++++++++++++++- .../src/main/browser-agent/cdp.test.ts | 131 +++++--- apps/desktop/src/main/browser-agent/cdp.ts | 78 +++-- .../src/main/browser-agent/driver.test.ts | 207 ++++++++++--- apps/desktop/src/main/browser-agent/driver.ts | 99 +++++-- .../main/browser-agent/page-functions.test.ts | 279 ++++++++++++++++++ .../src/main/browser-agent/page-functions.ts | 233 ++++++++++++--- .../lib/copilot/generated/tool-catalog-v1.ts | 45 ++- .../lib/copilot/generated/tool-schemas-v1.ts | 59 +++- .../client/browser-tool-execution.test.ts | 19 ++ .../tools/client/browser-tool-execution.ts | 5 +- .../tools/server/generated-schema.test.ts | 24 ++ 12 files changed, 1280 insertions(+), 175 deletions(-) diff --git a/apps/desktop/e2e/browser-tools.spec.ts b/apps/desktop/e2e/browser-tools.spec.ts index d7edc1bf521..fa12465e190 100644 --- a/apps/desktop/e2e/browser-tools.spec.ts +++ b/apps/desktop/e2e/browser-tools.spec.ts @@ -18,7 +18,15 @@ const SCOPE = 'browser-tools-e2e' const FORM = `Form fixture + + + + + + + + Other website @@ -27,6 +35,23 @@ const FORM = `Form fixture ` +const CLICK_FIXTURE = `Click fixture + + +` + test.describe('browser tools', () => { const calls = new Map< string, @@ -56,9 +81,11 @@ test.describe('browser tools', () => { } response.writeHead(200, { 'Content-Type': 'text/html' }) response.end( - path === '/form' - ? FORM - : 'Sim fixture

Browser tools fixture

' + path === '/click' + ? CLICK_FIXTURE + : path === '/form' + ? FORM + : 'Sim fixture

Browser tools fixture

' ) }) await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) @@ -78,15 +105,21 @@ test.describe('browser tools', () => { }, }) window = await app.firstWindow() + await app.evaluate(({ BrowserWindow }) => + BrowserWindow.getAllWindows()[0].webContents.setBackgroundThrottling(false) + ) await expect(window.getByRole('heading')).toHaveText('Browser tools fixture') await window.evaluate(async (scope) => { const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop await api.browserAgent.activateScope(scope) - api.browserAgent.setPanelBounds( - { x: 0, y: 80, width: innerWidth, height: innerHeight - 80 }, - null, - scope - ) + const updateBounds = () => + api.browserAgent.setPanelBounds( + { x: 0, y: 80, width: innerWidth, height: innerHeight - 80 }, + null, + scope + ) + updateBounds() + setInterval(updateBounds, 200) }, SCOPE) }) @@ -119,7 +152,9 @@ test.describe('browser tools', () => { const result = response.result as { snapshot: { outline: string } } expect(result.snapshot.outline).toContain('Name') return (name: string) => { - const line = result.snapshot.outline.split('\n').find((line) => line.includes(`"${name}"`)) + const line = result.snapshot.outline + .split('\n') + .find((line) => line.includes(`"${name}"`) && /\[ref=\d+\]/.test(line)) const match = line?.match(/\[ref=(\d+)\]/) if (!match) throw new Error(`No reference for ${name}: ${result.snapshot.outline}`) return Number(match[1]) @@ -143,6 +178,229 @@ test.describe('browser tools', () => { }, origin) } + test('sets and clears multiple selections without partial writes for invalid options', async () => { + const ref = await openForm() + const selected = await execute('browser_select_option', { + elementId: ref('Regions'), + values: ['A', 'B'], + }) + expect(selected.ok, selected.error).toBe(true) + expect(selected.result).toMatchObject({ + values: ['a', 'b'], + effectObserved: true, + readback: { values: ['a', 'b'] }, + }) + const invalid = await execute('browser_select_option', { + elementId: ref('Regions'), + values: ['B', 'C'], + }) + expect(invalid.ok).toBe(false) + const values = await app.evaluate(async ({ webContents }, origin) => { + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL() === `${origin}/form`) + if (!contents) throw new Error('Missing form fixture') + return contents.executeJavaScript( + 'Array.from(document.getElementById("regions").selectedOptions, option => option.value)' + ) + }, origin) + expect(values).toEqual(['a', 'b']) + const cleared = await execute('browser_select_option', { + elementId: ref('Regions'), + values: [], + }) + expect(cleared.result).toMatchObject({ + values: [], + effectObserved: true, + readback: { values: [] }, + }) + }) + + test('fills structured native fields and leaves invalid dates unchanged', async () => { + const ref = await openForm() + for (const [name, text] of [ + ['Date', '2026-09-15'], + ['Time', '15:48'], + ['Appointment', '2026-09-15T15:48:00'], + ['Month', '2026-09'], + ['Week', '2026-W38'], + ['Color', '#AABBCC'], + ['Range', '75'], + ]) { + const response = await execute('browser_type', { elementId: ref(name), text }) + expect(response.ok, response.error).toBe(true) + expect(response.result).toMatchObject({ + trusted: false, + dispatched: true, + effectObserved: true, + }) + } + const invalid = await execute('browser_type', { elementId: ref('Date'), text: '2026-02-30' }) + expect(invalid.ok).toBe(false) + expect(invalid.error).toContain('Invalid value') + const state = await app.evaluate(async ({ webContents }, origin) => { + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL() === `${origin}/form`) + if (!contents) throw new Error('Missing form fixture') + return contents.executeJavaScript( + '({date:document.getElementById("date").value,time:document.getElementById("time").value,appointment:document.getElementById("appointment").value,month:document.getElementById("month").value,week:document.getElementById("week").value,color:document.getElementById("color").value,range:document.getElementById("range").value,events:document.getElementById("date").dataset.events})' + ) + }, origin) + expect(state).toEqual({ + date: '2026-09-15', + time: '15:48', + appointment: '2026-09-15T15:48', + month: '2026-09', + week: '2026-W38', + color: '#aabbcc', + range: '75', + events: '1', + }) + }) + + for (const mode of ['menu', 'sticky']) { + test(`clicks a ${mode} target without losing its identity`, async () => { + const opened = await execute('browser_open_url', { url: `${origin}/click?mode=${mode}` }) + expect(opened.ok, opened.error).toBe(true) + await app.evaluate( + async ({ webContents }, { origin, mode }) => { + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL().startsWith(`${origin}/click`)) + if (!contents) throw new Error('Missing click fixture') + await contents.executeJavaScript(` + history.scrollRestoration = 'manual'; + document.getElementById('target').style.top = ${mode === 'sticky' ? '1010' : 'innerHeight - 100'} + 'px'; + scrollTo(0, ${mode === 'sticky' ? '1000' : '0'}); + new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => { + document.body.dataset.scrolls = '0'; document.body.dataset.armed = 'true'; resolve(); + }))) + `) + }, + { origin, mode } + ) + const snapshot = await execute('browser_snapshot', {}) + expect(snapshot.ok, snapshot.error).toBe(true) + const outline = (snapshot.result as { outline: string }).outline + const line = outline.split('\n').find((line) => line.includes('"Choose option"')) + const match = line?.match(/\[ref=(\d+)\]/) + if (!match) throw new Error(`Missing target: ${outline}`) + const result = await execute('browser_click', { elementId: Number(match[1]) }) + expect(result.ok, result.error).toBe(true) + const state = await app.evaluate(async ({ webContents }, origin) => { + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL().startsWith(`${origin}/click`)) + if (!contents) throw new Error('Missing click fixture') + return contents.executeJavaScript( + '({clicks:document.body.dataset.clicks,scrolls:document.body.dataset.scrolls,scrollY})' + ) + }, origin) + expect(state.clicks).toBe('1') + if (mode === 'menu') expect(state).toMatchObject({ scrolls: '0', scrollY: 0 }) + else expect(state.scrollY).toBeLessThan(1000) + }) + } + + for (const mode of ['visible', 'hidden', 'minimized']) { + test(`captures a ${mode} window without changing its state`, async () => { + test.skip( + mode === 'minimized' && process.platform !== 'darwin', + 'Requires a window manager with minimize events' + ) + await openForm() + await app.evaluate(async ({ BrowserWindow }, mode) => { + const win = BrowserWindow.getAllWindows()[0] + win.blur() + if (mode === 'hidden') win.hide() + if (mode === 'minimized') { + const minimized = new Promise((resolve) => win.once('minimize', () => resolve())) + win.minimize() + await minimized + } + }, mode) + const state = () => + app.evaluate(async ({ BrowserWindow, webContents }, origin) => { + const win = BrowserWindow.getAllWindows()[0] + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL() === `${origin}/form`) + if (!contents) throw new Error('Missing screenshot fixture') + return { + visible: win.isVisible(), + minimized: win.isMinimized(), + bounds: win.getBounds(), + focused: BrowserWindow.getFocusedWindow()?.id ?? null, + page: await contents.executeJavaScript( + '({width:innerWidth,height:innerHeight,scrollX,scrollY,html:document.body.innerHTML,focus:document.activeElement?.id})' + ), + } + }, origin) + const before = await state() + for (let i = 0; i < 3; i++) { + const response = await execute('browser_screenshot', {}) + expect(response.ok, response.error).toBe(true) + const shot = response.result as { + dataUrl: string + scale: number + viewport: { width: number; height: number } + } + expect(shot.dataUrl.length).toBeGreaterThan(1000) + expect(shot.viewport.width).toBeGreaterThan(0) + expect(shot.viewport.height).toBeGreaterThan(0) + const image = await app.evaluate(({ nativeImage }, dataUrl) => { + const image = nativeImage.createFromDataURL(dataUrl) + return { empty: image.isEmpty(), ...image.getSize() } + }, shot.dataUrl) + expect(image).toEqual({ + empty: false, + width: Math.round(shot.viewport.width * shot.scale), + height: Math.round(shot.viewport.height * shot.scale), + }) + expect(await state()).toEqual(before) + } + }) + } + + test('captures fresh pixels after resizing and repainting the viewport', async () => { + await openForm() + const viewportWidth = () => + app.evaluate(async ({ webContents }, origin) => { + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL() === `${origin}/form`) + return contents?.executeJavaScript('innerWidth') + }, origin) + const beforeWidth = await viewportWidth() + await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].setSize(1280, 900)) + await expect.poll(viewportWidth).not.toBe(beforeWidth) + for (const color of ['red', 'blue']) { + await app.evaluate( + async ({ webContents }, { origin, color }) => { + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL() === `${origin}/form`) + if (!contents) throw new Error('Missing screenshot fixture') + await contents.executeJavaScript( + `document.body.style.background = ${JSON.stringify(color)}; void 0` + ) + }, + { origin, color } + ) + const response = await execute('browser_screenshot', {}) + expect(response.ok, response.error).toBe(true) + const shot = response.result as { dataUrl: string } + const pixel = await app.evaluate(({ nativeImage }, dataUrl) => { + const image = nativeImage.createFromDataURL(dataUrl) + return Array.from(image.toBitmap().subarray(0, 4)) + }, shot.dataUrl) + const dominant = pixel[color === 'red' ? 2 : 0] + const other = pixel[color === 'red' ? 0 : 2] + expect(dominant - other, `${color}: ${pixel}`).toBeGreaterThan(150) + } + }) + test('opens with references, fills in order, and scrolls a horizontal pane', async () => { const ref = await openForm() const fill = await execute('browser_fill_form', { diff --git a/apps/desktop/src/main/browser-agent/cdp.test.ts b/apps/desktop/src/main/browser-agent/cdp.test.ts index c65c987f361..9f7b32b6071 100644 --- a/apps/desktop/src/main/browser-agent/cdp.test.ts +++ b/apps/desktop/src/main/browser-agent/cdp.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest' vi.mock('electron', () => import('@/test/electron-mock')) -import { nativeImage, type WebContents, WebContentsView, type WebFrameMain } from 'electron' +import { type nativeImage, WebContentsView, type WebFrameMain } from 'electron' import { captureScreenshot, clickAt, @@ -497,7 +497,6 @@ describe('browser-agent screenshot capture', () => { if (method === 'Page.getLayoutMetrics') { return Promise.resolve({ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024 } }) } - if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' }) return Promise.resolve(undefined) }) const resized = { @@ -509,25 +508,15 @@ describe('browser-agent screenshot capture', () => { resize: vi.fn(() => resized), toJPEG: vi.fn(() => Buffer.from('cropped')), } - // Shared module-level mock: without this, a later fixture reads the - // earlier test's decoded image. - vi.mocked(nativeImage.createFromBuffer).mockReset() - vi.mocked(nativeImage.createFromBuffer).mockReturnValue({ + const image = { isEmpty: vi.fn(() => imageSize === null), getSize: vi.fn(() => imageSize ?? { width: 0, height: 0 }), crop: vi.fn(() => cropped), resize: vi.fn(() => resized), - toJPEG: vi.fn(() => Buffer.alloc(0)), - } as unknown as ReturnType) - return { contents, resized, cropped } - } - - function screenshotParams(contents: WebContents): Record { - const call = vi - .mocked(contents.debugger.sendCommand) - .mock.calls.find(([method]) => method === 'Page.captureScreenshot') - if (!call) throw new Error('no capture was requested') - return call[1] as Record + toJPEG: vi.fn(() => Buffer.from('sim')), + } as unknown as ReturnType + vi.mocked(contents.capturePage).mockResolvedValue(image) + return { contents, resized, cropped, image } } it('never sends a clip, which would emulate the live page for the capture', async () => { @@ -535,16 +524,23 @@ describe('browser-agent screenshot capture', () => { await captureScreenshot(contents) - expect(screenshotParams(contents)).not.toHaveProperty('clip') + expect(contents.capturePage).toHaveBeenCalledWith(undefined, { stayHidden: true }) + expect(contents.debugger.sendCommand).not.toHaveBeenCalledWith( + 'Page.captureScreenshot', + expect.anything() + ) }) it('crops the decoded image in memory without sending a CDP clip', async () => { - const { contents, cropped } = captureFixture({ width: 4096, height: 2048 }) + const { contents, cropped, image } = captureFixture({ width: 4096, height: 2048 }) const shot = await captureScreenshot(contents, { x: 100, y: 50, width: 200, height: 100 }) - const image = vi.mocked(nativeImage.createFromBuffer).mock.results[0].value - expect(screenshotParams(contents)).not.toHaveProperty('clip') + expect(contents.capturePage).toHaveBeenCalledWith(undefined, { stayHidden: true }) + expect(contents.debugger.sendCommand).not.toHaveBeenCalledWith( + 'Page.captureScreenshot', + expect.anything() + ) expect(image.crop).toHaveBeenCalledWith({ x: 200, y: 100, width: 400, height: 200 }) expect(cropped.resize).not.toHaveBeenCalled() expect(shot).toEqual({ @@ -562,11 +558,10 @@ describe('browser-agent screenshot capture', () => { * (cssX = imageX / scale) assumes. */ it('downscales the returned image to the CSS-relative size', async () => { - const { contents, resized } = captureFixture({ width: 4096, height: 2048 }) + const { contents, resized, image } = captureFixture({ width: 4096, height: 2048 }) const shot = await captureScreenshot(contents) - const image = vi.mocked(nativeImage.createFromBuffer).mock.results[0].value expect(image.resize).toHaveBeenCalledWith({ width: 1024, height: 512, quality: 'good' }) expect(resized.toJPEG).toHaveBeenCalled() expect(shot).toEqual({ @@ -577,12 +572,11 @@ describe('browser-agent screenshot capture', () => { }) }) - it('skips the re-encode when the capture already matches the target size', async () => { - const { contents } = captureFixture({ width: 1024, height: 512 }) + it('skips resizing when the capture already matches the target size', async () => { + const { contents, image } = captureFixture({ width: 1024, height: 512 }) const shot = await captureScreenshot(contents) - const image = vi.mocked(nativeImage.createFromBuffer).mock.results[0].value expect(image.resize).not.toHaveBeenCalled() expect(shot).toEqual({ dataUrl: 'data:image/jpeg;base64,c2lt', @@ -592,16 +586,82 @@ describe('browser-agent screenshot capture', () => { }) }) - it('returns the raw capture when the image cannot be decoded', async () => { + it('rejects an empty native capture', async () => { const { contents } = captureFixture(null) + await expect(captureScreenshot(contents)).rejects.toThrow('empty image') + }) - const shot = await captureScreenshot(contents) + it('bounds a stalled capture and prevents overlapping native surface copies', async () => { + vi.useFakeTimers() + try { + const { contents, image } = captureFixture({ width: 1024, height: 512 }) + let release: (captured: typeof image) => void = () => {} + vi.mocked(contents.capturePage).mockImplementationOnce( + () => + new Promise((resolve) => { + release = resolve + }) + ) + const failed = expect(captureScreenshot(contents)).rejects.toThrow('pixel capture timed out') + await vi.advanceTimersByTimeAsync(5_000) + await failed + expect(vi.getTimerCount()).toBe(0) + await expect(captureScreenshot(contents)).rejects.toThrow( + 'previous screenshot capture is still pending' + ) + expect(contents.capturePage).toHaveBeenCalledOnce() + release(image) + await Promise.resolve() + await expect(captureScreenshot(contents)).resolves.toMatchObject({ + imageSize: { width: 1024, height: 512 }, + }) + expect(contents.capturePage).toHaveBeenCalledTimes(2) + } finally { + vi.useRealTimers() + } + }) - expect(shot).toEqual({ - dataUrl: 'data:image/jpeg;base64,c2lt', - scale: 0.5, - viewport: { width: 2048, height: 1024 }, - imageSize: null, + it.each(['cancel', 'destroy'] as const)( + 'releases capture listeners and timer on %s', + async (reason) => { + vi.useFakeTimers() + try { + const { contents } = captureFixture({ width: 1024, height: 512 }) + vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) + const controller = new AbortController() + const failed = expect( + captureScreenshot(contents, undefined, controller.signal) + ).rejects.toThrow(reason === 'cancel' ? 'cancelled' : 'tab was closed') + await vi.advanceTimersByTimeAsync(0) + const destroyed = vi + .mocked(contents.once) + .mock.calls.find(([event]) => String(event) === 'destroyed')?.[1] as unknown as + | (() => void) + | undefined + expect(destroyed).toBeDefined() + if (reason === 'cancel') controller.abort() + else destroyed?.() + await failed + expect(contents.removeListener).toHaveBeenCalledWith('destroyed', destroyed) + expect(vi.getTimerCount()).toBe(0) + } finally { + vi.useRealTimers() + } + } + ) + + it('does not start capture after cancellation or keep a synchronous failure pending', async () => { + const { contents } = captureFixture({ width: 1024, height: 512 }) + const controller = new AbortController() + controller.abort() + await expect(captureScreenshot(contents, undefined, controller.signal)).rejects.toThrow() + expect(contents.capturePage).not.toHaveBeenCalled() + vi.mocked(contents.capturePage).mockImplementationOnce(() => { + throw new Error('native failure') + }) + await expect(captureScreenshot(contents)).rejects.toThrow('native failure') + await expect(captureScreenshot(contents)).resolves.toMatchObject({ + imageSize: { width: 1024, height: 512 }, }) }) @@ -611,7 +671,6 @@ describe('browser-agent screenshot capture', () => { if (method === 'Page.getLayoutMetrics') { return Promise.resolve({ layoutViewport: { clientWidth: 2048, clientHeight: 1024 } }) } - if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' }) return Promise.resolve(undefined) }) @@ -652,7 +711,6 @@ describe('browser-agent screenshot capture', () => { }, }) } - if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' }) return Promise.resolve(undefined) }) @@ -697,7 +755,7 @@ describe('browser-agent screenshot capture', () => { ], ['availability', {}, {}], ])( - 'rejects a capture when viewport %s change during CDP capture', + 'rejects a capture when viewport %s change during native capture', async (_label, before, after) => { const { contents } = captureFixture({ width: 1024, height: 512 }) let metricsRead = 0 @@ -706,7 +764,6 @@ describe('browser-agent screenshot capture', () => { metricsRead++ return Promise.resolve(metricsRead === 1 ? before : after) } - if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' }) return Promise.resolve(undefined) }) diff --git a/apps/desktop/src/main/browser-agent/cdp.ts b/apps/desktop/src/main/browser-agent/cdp.ts index e966850aaf2..7c30dc6bd07 100644 --- a/apps/desktop/src/main/browser-agent/cdp.ts +++ b/apps/desktop/src/main/browser-agent/cdp.ts @@ -11,7 +11,7 @@ import type { BrowserTheme } from '@sim/browser-protocol' import { createLogger } from '@sim/logger' import { sleep } from '@sim/utils/helpers' -import { nativeImage, type WebContents, type WebFrameMain } from 'electron' +import type { NativeImage, WebContents, WebFrameMain } from 'electron' const logger = createLogger('BrowserAgentCdp') @@ -370,14 +370,10 @@ export async function evaluateInIsolatedFrame( */ const MAX_SCREENSHOT_EDGE = 1024 const SCREENSHOT_QUALITY = 70 -/** - * Quality of the intermediate capture, before the in-process downscale - * re-encodes at {@link SCREENSHOT_QUALITY}. Higher than the final quality so - * the two lossy passes together land near where one pass did — the model reads - * text out of these frames, and compression artifacts on glyphs cost more than - * the transient bytes do. - */ -const SCREENSHOT_CAPTURE_QUALITY = 90 +const UNSCALED_SCREENSHOT_QUALITY = 90 +const SCREENSHOT_CAPTURE_TIMEOUT_MS = 5_000 +/** Native surface copies cannot be cancelled; never accumulate them on a stalled tab. */ +const pendingScreenshotCaptures = new WeakSet() interface CdpViewport { clientWidth: number @@ -401,7 +397,7 @@ export interface ScreenshotCapture { dataUrl: string scale: number viewport: ScreenshotSize | null - imageSize: ScreenshotSize | null + imageSize: ScreenshotSize } export interface ScreenshotClip { @@ -456,8 +452,48 @@ function sameScreenshotViewport( ) } +/** Captures pixels without changing viewport geometry or exposing a hidden window. */ +async function captureViewportImage( + contents: WebContents, + signal?: AbortSignal +): Promise { + signal?.throwIfAborted() + if (contents.isDestroyed()) throw new Error('The screenshot tab was closed') + if (pendingScreenshotCaptures.has(contents)) { + throw new Error('A previous screenshot capture is still pending on this tab') + } + pendingScreenshotCaptures.add(contents) + let timer: ReturnType | undefined + let onAbort = () => {} + let onDestroyed = () => {} + try { + const interrupted = new Promise((_resolve, reject) => { + onAbort = () => reject(new Error('Screenshot capture was cancelled')) + onDestroyed = () => reject(new Error('The screenshot tab was closed')) + signal?.addEventListener('abort', onAbort, { once: true }) + contents.once('destroyed', onDestroyed) + timer = setTimeout( + () => reject(new Error('Screenshot pixel capture timed out after 5 seconds')), + SCREENSHOT_CAPTURE_TIMEOUT_MS + ) + }) + const capture = (async () => { + try { + return await contents.capturePage(undefined, { stayHidden: true }) + } finally { + pendingScreenshotCaptures.delete(contents) + } + })() + return await Promise.race([capture, interrupted]) + } finally { + clearTimeout(timer) + signal?.removeEventListener('abort', onAbort) + contents.removeListener('destroyed', onDestroyed) + } +} + /** - * Screenshot via CDP (works while the view is hidden), bounded in resolution. + * Native viewport capture, bounded in time and resolution. * * The capture is deliberately UNCLIPPED. Chromium implements `clip` by applying * device-emulation parameters (viewport offset and scale) to the widget and @@ -473,7 +509,8 @@ function sameScreenshotViewport( */ export async function captureScreenshot( contents: WebContents, - clip?: ScreenshotClip + clip?: ScreenshotClip, + signal?: AbortSignal ): Promise { const metrics = await send<{ cssLayoutViewport?: CdpViewport @@ -492,10 +529,7 @@ export async function captureScreenshot( const scale = width > 0 && height > 0 ? Math.min(1, MAX_SCREENSHOT_EDGE / Math.max(width, height)) : 1 - const result = await send<{ data: string }>(contents, 'Page.captureScreenshot', { - format: 'jpeg', - quality: SCREENSHOT_CAPTURE_QUALITY, - }) + const image = await captureViewportImage(contents, signal) const metricsAfterCapture = await send<{ cssLayoutViewport?: CdpViewport layoutViewport?: CdpViewport @@ -503,15 +537,12 @@ export async function captureScreenshot( if (!sameScreenshotViewport(captureViewport, screenshotViewportMetrics(metricsAfterCapture))) { throw new Error('The page viewport changed or could not be verified during screenshot capture') } - const captured = `data:image/jpeg;base64,${result.data}` const targetWidth = Math.round(width * scale) const targetHeight = Math.round(height * scale) - const image = nativeImage.createFromBuffer(Buffer.from(result.data, 'base64')) const size = image.isEmpty() ? { width: 0, height: 0 } : image.getSize() if (size.width === 0 || size.height === 0) { - if (clip) throw new Error('The screenshot could not be decoded for element cropping') - return { dataUrl: captured, scale, viewport: cssViewport, imageSize: null } + throw new Error('Screenshot pixel capture returned an empty image') } if (clip && cssViewport) { const xScale = size.width / cssViewport.width @@ -554,7 +585,12 @@ export async function captureScreenshot( } } if (size.width === targetWidth && size.height === targetHeight) { - return { dataUrl: captured, scale, viewport: cssViewport, imageSize: size } + return { + dataUrl: `data:image/jpeg;base64,${image.toJPEG(UNSCALED_SCREENSHOT_QUALITY).toString('base64')}`, + scale, + viewport: cssViewport, + imageSize: size, + } } const resized = image.resize({ width: targetWidth, height: targetHeight, quality: 'good' }) diff --git a/apps/desktop/src/main/browser-agent/driver.test.ts b/apps/desktop/src/main/browser-agent/driver.test.ts index 5cb48a1cd3c..285112fb61c 100644 --- a/apps/desktop/src/main/browser-agent/driver.test.ts +++ b/apps/desktop/src/main/browser-agent/driver.test.ts @@ -1,10 +1,10 @@ import { BROWSER_TOOL_QUEUE_WAIT_TIMEOUT_MS } from '@sim/browser-protocol' -import type { MenuItemConstructorOptions } from 'electron' +import type { MenuItemConstructorOptions, WebContents } from 'electron' import { beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('electron', () => import('@/test/electron-mock')) -import { BrowserWindow, Menu, nativeImage } from 'electron' +import { BrowserWindow, Menu, type nativeImage } from 'electron' import * as cdp from '@/main/browser-agent/cdp' import * as driverModule from '@/main/browser-agent/driver' import * as session from '@/main/browser-agent/session' @@ -485,8 +485,11 @@ describe('executeTool', () => { ) await Promise.resolve() expect(captureScreenshot).toHaveBeenCalledOnce() + const signal = captureScreenshot.mock.calls[0][2] + expect(signal?.aborted).toBe(false) driver.disposeBrowserScope('chat-test') + expect(signal?.aborted).toBe(true) automationTab.mockClear() await expect(screenshot).resolves.toMatchObject({ ok: false, @@ -2142,6 +2145,50 @@ describe('credential protection', () => { return { contents, values, writes, dialogs, selectionReads: () => selectionReads } } + it.each([ + { values: ['a', 'b'], labels: ['A', 'B'], expected: true }, + { values: ['a'], labels: ['A'], expected: false }, + { values: ['a', 'b'], labels: ['A', 'Other'], expected: false }, + ])( + 'verifies the entire multiple selection %j', + async ({ values: readbackValues, labels, expected }) => { + const contents = await openPage() + respondWith(contents, { + selectOptionInElement: { + selected: 'A', + value: 'a', + values: ['a', 'b'], + labels: ['A', 'B'], + }, + readSelectElementState: { selected: 'A', value: 'a', values: readbackValues, labels }, + }) + const result = await driver.executeTool('chat-test', 'browser_select_option', { + elementId: 0, + values: ['a', 'b'], + }) + expect(result, JSON.stringify(result)).toMatchObject({ + ok: true, + result: { effectObserved: expected, readback: { values: readbackValues } }, + }) + } + ) + + it.each([ + { value: 'a', values: ['b'] }, + { values: [1] }, + { values: Array.from({ length: 101 }, () => 'a') }, + {}, + ])('rejects invalid selection arguments before dispatch', async (params) => { + const contents = await openPage() + vi.mocked(contents.executeJavaScript).mockClear() + const result = await driver.executeTool('chat-test', 'browser_select_option', { + elementId: 0, + ...params, + }) + expect(result.ok).toBe(false) + expect(contents.executeJavaScript).not.toHaveBeenCalled() + }) + const formFields = [ { elementId: 1, kind: 'select', value: 'first' }, { elementId: 2, kind: 'select', value: 'second' }, @@ -2282,8 +2329,11 @@ describe('credential protection', () => { expect(form.writes).toEqual([0]) }) - function mockScreenshotImage(size: { width: number; height: number } | null): void { - vi.mocked(nativeImage.createFromBuffer).mockReturnValueOnce({ + function mockScreenshotImage( + contents: WebContents, + size: { width: number; height: number } | null + ): void { + vi.mocked(contents.capturePage).mockResolvedValue({ isEmpty: vi.fn(() => size === null), getSize: vi.fn(() => size ?? { width: 0, height: 0 }), resize: vi.fn(() => ({ toJPEG: vi.fn(() => Buffer.from('resized')) })), @@ -2469,6 +2519,97 @@ describe('credential protection', () => { expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(1) }) + it('sets structured input values without dispatching text or select-all keystrokes', async () => { + const contents = await openPage() + respondWith(contents, { + focusElementForTyping: { focused: true, kind: 'input', valueInput: true, x: 24, y: 48 }, + setFocusedInputValue: { dispatched: true }, + readActiveElementState: { activeElement: 'input', valueLength: 10 }, + readPageActionState: {}, + }) + const result = await driver.executeTool('chat-test', 'browser_type', { + elementId: 0, + text: '2026-09-15', + }) + expect(result).toMatchObject({ ok: true, result: { dispatched: true, trusted: false } }) + expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) + expect(cdpCalls(contents, 'Input.dispatchKeyEvent')).toHaveLength(0) + }) + + it('does not retry a rejected structured value through synthetic typing', async () => { + const contents = await openPage() + respondWith(contents, { + focusElementForTyping: { focused: true, kind: 'input', valueInput: true, x: 24, y: 48 }, + setFocusedInputValue: { error: 'Invalid value; the field was not changed.' }, + readActiveElementState: {}, + readPageActionState: {}, + }) + const result = await driver.executeTool('chat-test', 'browser_type', { + elementId: 0, + text: 'invalid-date', + }) + expect(result).toMatchObject({ ok: false, error: expect.stringContaining('Invalid value') }) + expect( + vi + .mocked(contents.executeJavaScript) + .mock.calls.filter(([expression]) => isPageCall(String(expression), 'typeIntoElement')) + ).toHaveLength(0) + expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) + }) + + it('reports an interrupted structured write as uncertain without replaying it', async () => { + const contents = await openPage() + let writes = 0 + vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { + if (isPageCall(expression, 'focusElementForTyping')) + return Promise.resolve({ focused: true, valueInput: true, x: 24, y: 48 }) + if (isPageCall(expression, 'setFocusedInputValue')) { + writes++ + return Promise.reject(new Error('Execution context was destroyed')) + } + return Promise.resolve({}) + }) + const result = await driver.executeTool('chat-test', 'browser_type', { + elementId: 0, + text: '2026-09-15', + }) + expect(result).toMatchObject({ + ok: false, + error: expect.stringContaining('may have reached the field and was not retried'), + }) + expect(writes).toBe(1) + expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) + expect( + vi + .mocked(contents.executeJavaScript) + .mock.calls.some(([expression]) => isPageCall(String(expression), 'typeIntoElement')) + ).toBe(false) + }) + + it('refuses a field whose input mode changes before dispatch', async () => { + const contents = await openPage() + let reads = 0 + vi.mocked(contents.executeJavaScript).mockImplementation((expression: string) => { + if (isPageCall(expression, 'focusElementForTyping')) + return Promise.resolve({ focused: true, valueInput: ++reads === 1, x: 24, y: 48 }) + return Promise.resolve({}) + }) + const result = await driver.executeTool('chat-test', 'browser_type', { + elementId: 0, + text: '2026-09-15', + }) + expect(result).toMatchObject({ + ok: false, + error: expect.stringContaining('field type changed'), + }) + expect(cdpCalls(contents, 'Input.insertText')).toHaveLength(0) + expect( + vi + .mocked(contents.executeJavaScript) + .mock.calls.some(([expression]) => isPageCall(String(expression), 'setFocusedInputValue')) + ).toBe(false) + }) + it('accepts empty text and sends it through native insertion to clear a field', async () => { const contents = await openPage() respondWith(contents, { @@ -3966,7 +4107,11 @@ describe('credential protection', () => { try { const result = await driver.executeTool('chat-test', 'browser_screenshot', { elementId: 0 }) - expect(capture).toHaveBeenCalledWith(contents, { x: 20, y: 30, width: 200, height: 100 }) + expect(capture).toHaveBeenCalledWith( + contents, + { x: 20, y: 30, width: 200, height: 100 }, + expect.any(AbortSignal) + ) expect(result).toMatchObject({ ok: true, result: { element: 'button', clip: { x: 20, y: 30, width: 200, height: 100 } }, @@ -4009,16 +4154,13 @@ describe('credential protection', () => { it('returns the screenshot scale for coordinate mapping', async () => { const contents = await openPage() - mockScreenshotImage({ width: 1024, height: 512 }) + mockScreenshotImage(contents, { width: 1024, height: 512 }) vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { if (method === 'Page.getLayoutMetrics') { return Promise.resolve({ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024 }, }) } - if (method === 'Page.captureScreenshot') { - return Promise.resolve({ data: 'c2lt' }) - } return Promise.resolve(undefined) }) respondWith(contents, { getViewportInfo: { width: 2048, height: 1024 } }) @@ -4046,14 +4188,11 @@ describe('credential protection', () => { it('uses the in-page CSS viewport when CDP exposes only deprecated device metrics', async () => { const contents = await openPage() - mockScreenshotImage({ width: 1024, height: 512 }) + mockScreenshotImage(contents, { width: 1024, height: 512 }) vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { if (method === 'Page.getLayoutMetrics') { return Promise.resolve({ layoutViewport: { clientWidth: 2048, clientHeight: 1024 } }) } - if (method === 'Page.captureScreenshot') { - return Promise.resolve({ data: 'c2lt' }) - } return Promise.resolve(undefined) }) respondWith(contents, { @@ -4102,12 +4241,11 @@ describe('credential protection', () => { const fullTitle = `Example ${'t'.repeat(600)}` vi.mocked(contents.getURL).mockReturnValue(fullUrl) vi.mocked(contents.getTitle).mockReturnValue(fullTitle) - mockScreenshotImage({ width: 1024, height: 512 }) + mockScreenshotImage(contents, { width: 1024, height: 512 }) vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { if (method === 'Page.getLayoutMetrics') { return Promise.resolve({ layoutViewport: { clientWidth: 2048, clientHeight: 1024 } }) } - if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' }) return Promise.resolve(undefined) }) respondWith(contents, { @@ -4135,33 +4273,31 @@ describe('credential protection', () => { }) }) - it('rejects an undecodable screenshot instead of returning an unverified scale', async () => { + it('rejects an empty screenshot instead of returning an unverified scale', async () => { const contents = await openPage() - mockScreenshotImage(null) + mockScreenshotImage(contents, null) vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { if (method === 'Page.getLayoutMetrics') { return Promise.resolve({ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024 }, }) } - if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' }) return Promise.resolve(undefined) }) const result = await driver.executeTool('chat-test', 'browser_screenshot', {}) expect(result.ok).toBe(false) - expect(result.error).toMatch(/verify the screenshot dimensions/) + expect(result.error).toMatch(/empty image/) }) it('rejects a screenshot when no CSS viewport can be established', async () => { const contents = await openPage() - mockScreenshotImage({ width: 1024, height: 512 }) + mockScreenshotImage(contents, { width: 1024, height: 512 }) vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { if (method === 'Page.getLayoutMetrics') { return Promise.resolve({ layoutViewport: { clientWidth: 2048, clientHeight: 1024 } }) } - if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' }) return Promise.resolve(undefined) }) respondWith(contents, { getViewportInfo: null }) @@ -4174,12 +4310,11 @@ describe('credential protection', () => { it('rejects coordinate mapping when the viewport changes during capture', async () => { const contents = await openPage() - mockScreenshotImage({ width: 1024, height: 256 }) + mockScreenshotImage(contents, { width: 1024, height: 256 }) vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { if (method === 'Page.getLayoutMetrics') { return Promise.resolve({ layoutViewport: { clientWidth: 1024, clientHeight: 256 } }) } - if (method === 'Page.captureScreenshot') return Promise.resolve({ data: 'c2lt' }) return Promise.resolve(undefined) }) respondWith(contents, { @@ -4199,20 +4334,22 @@ describe('credential protection', () => { it('rejects a screenshot when the document navigates during capture', async () => { const contents = await openPage() - mockScreenshotImage({ width: 1024, height: 512 }) + mockScreenshotImage(contents, { width: 1024, height: 512 }) vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { if (method === 'Page.getLayoutMetrics') { return Promise.resolve({ cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024 }, }) } - if (method === 'Page.captureScreenshot') { - emitContentsEvent(contents, 'did-navigate') - return Promise.resolve({ data: 'c2lt' }) - } return Promise.resolve(undefined) }) + const image = await contents.capturePage() + vi.mocked(contents.capturePage).mockImplementation(async () => { + emitContentsEvent(contents, 'did-navigate') + return image + }) + const result = await driver.executeTool('chat-test', 'browser_screenshot', {}) expect(result.ok).toBe(false) @@ -4223,7 +4360,7 @@ describe('credential protection', () => { 'rejects a screenshot when the page %s changes during capture', async (identityField) => { const contents = await openPage() - mockScreenshotImage({ width: 1024, height: 512 }) + mockScreenshotImage(contents, { width: 1024, height: 512 }) const initialUrl = contents.getURL() const initialTitle = contents.getTitle() let currentUrl = initialUrl @@ -4236,14 +4373,16 @@ describe('credential protection', () => { cssLayoutViewport: { clientWidth: 2048, clientHeight: 1024 }, }) } - if (method === 'Page.captureScreenshot') { - if (identityField === 'url') currentUrl = 'https://example.com/changed' - else currentTitle = 'Changed title' - return Promise.resolve({ data: 'c2lt' }) - } return Promise.resolve(undefined) }) + const image = await contents.capturePage() + vi.mocked(contents.capturePage).mockImplementation(async () => { + if (identityField === 'url') currentUrl = 'https://example.com/changed' + else currentTitle = 'Changed title' + return image + }) + const result = await driver.executeTool('chat-test', 'browser_screenshot', {}) expect(result.ok).toBe(false) diff --git a/apps/desktop/src/main/browser-agent/driver.ts b/apps/desktop/src/main/browser-agent/driver.ts index 807aaf81444..94b53b9db52 100644 --- a/apps/desktop/src/main/browser-agent/driver.ts +++ b/apps/desktop/src/main/browser-agent/driver.ts @@ -66,6 +66,7 @@ import { readSelectElementState, scrollPage, selectOptionInElement, + setFocusedInputValue, typeIntoElement, } from '@/main/browser-agent/page-functions' import * as session from '@/main/browser-agent/session' @@ -1290,6 +1291,7 @@ function unwrapPageResult(result: unknown): unknown { `No option matched that label or value. Available options: ${options.join(', ')}` ) } + throw new ToolError(String(code)) } return result } @@ -2278,7 +2280,8 @@ async function executeToolInner( params: Record, assertCurrentExecution: () => void, executionDeadline: number | undefined, - invocationEpoch: number + invocationEpoch: number, + signal?: AbortSignal ): Promise { switch (tool) { case 'browser_navigate': { @@ -2630,15 +2633,11 @@ async function executeToolInner( } : undefined assertCaptureIsCurrent() - const shot = await cdp.captureScreenshot(contents, clip).catch((error) => { - logger.warn('Browser screenshot capture failed', { error: getErrorMessage(error) }) - return null - }) - if (!shot) { + const shot = await cdp.captureScreenshot(contents, clip, signal).catch((error) => { throw new ToolError( - 'Could not capture the page. Use browser_snapshot or browser_read_text instead.' + `Could not capture the page: ${getErrorMessage(error)}. Use browser_snapshot or browser_read_text instead.` ) - } + }) assertCaptureIsCurrent() if (elementId !== undefined && elementClip) { const currentClip = toRecord( @@ -2664,11 +2663,6 @@ async function executeToolInner( 'The screenshot result was too large to return safely. Use browser_snapshot or browser_read_text instead.' ) } - if (!shot.imageSize) { - throw new ToolError( - 'Could not verify the screenshot dimensions. Retry browser_screenshot or use browser_snapshot instead.' - ) - } const viewport = shot.viewport ? { url: capturedViewportUrl, @@ -3344,16 +3338,19 @@ async function executeToolInner( assertCurrentExecution() assertElementActionCurrent(contents, elementId, target) } - let trusted = true + const valueInput = initialSurface.valueInput === true + let trusted = !valueInput let nativeInserted = false let nativeInsertAttempted = false try { assertCurrentExecution() assertElementActionCurrent(contents, elementId, target) - await dispatchKeyCombo( - contents, - parseKeyCombo(process.platform === 'darwin' ? 'Cmd+A' : 'Control+A') - ) + if (!valueInput) { + await dispatchKeyCombo( + contents, + parseKeyCombo(process.platform === 'darwin' ? 'Cmd+A' : 'Control+A') + ) + } // The guard above vetted the element we asked to focus, but the insert // below goes wherever focus actually is now, a round trip later. Login // forms that auto-advance from username to password move it in exactly @@ -3406,8 +3403,32 @@ async function executeToolInner( } assertCurrentExecution() assertElementActionCurrent(contents, elementId, target) + if ((finalSurface.valueInput === true) !== valueInput) { + throw new ToolError('The field type changed before input. Take a fresh browser_snapshot.') + } nativeInsertAttempted = true - await cdp.insertText(contents, text) + if (valueInput) { + const written = unwrapPageResult( + await execInPage( + target, + setFocusedInputValue, + [elementId, text], + false, + executionDeadline + ).catch((error) => { + throw new ToolError( + `The structured field write did not acknowledge completion (${getErrorMessage(error)}). It may have reached the field and was not retried; inspect the page before continuing.` + ) + }) + ) + if (!isRecordLike(written) || written.dispatched !== true) { + throw new ToolError( + 'The field did not acknowledge the value write. Inspect it before retrying.' + ) + } + } else { + await cdp.insertText(contents, text) + } nativeInserted = true let submitted = false @@ -3845,6 +3866,19 @@ async function executeToolInner( } case 'browser_select_option': { + const values = params.values + if (values !== undefined && params.value !== undefined) { + throw new ToolError('Provide value or values, not both.') + } + if ( + values !== undefined && + (!Array.isArray(values) || + values.length > 100 || + values.some((value) => typeof value !== 'string')) + ) { + throw new ToolError('values must be an array of at most 100 strings.') + } + const selection = values === undefined ? requireStr(params, 'value') : (values as string[]) const contents = session.requireAutomationTab().view.webContents const elementId = requireNum(params, 'elementId') const target = pageTargetForElement(contents, elementId) @@ -3880,7 +3914,7 @@ async function executeToolInner( await execInPage( target, selectOptionInElement, - [elementId, requireStr(params, 'value')], + [elementId, selection], false, executionDeadline ) @@ -3894,10 +3928,22 @@ async function executeToolInner( } await sleep(50) const state = unwrapPageResult(await execInPage(target, readSelectElementState, [elementId])) + const selectedValues = selected.values + const readbackValues = isRecordLike(state) ? state.values : undefined + const selectedLabels = selected.labels + const readbackLabels = isRecordLike(state) ? state.labels : undefined const effectObserved = isRecordLike(state) && selected.selected === state.selected && - selected.value === state.value + selected.value === state.value && + (!Array.isArray(selectedValues) || + (Array.isArray(readbackValues) && + selectedValues.length === readbackValues.length && + selectedValues.every((value, index) => value === readbackValues[index]) && + Array.isArray(selectedLabels) && + Array.isArray(readbackLabels) && + selectedLabels.length === readbackLabels.length && + selectedLabels.every((label, index) => label === readbackLabels[index]))) return { ...selected, effectObserved, @@ -4599,9 +4645,13 @@ export async function executeTool( throw new ToolError('This browser action was cancelled before it started.') } state.activeToolCallId = toolCallId ?? null + const executionController = new AbortController() let cancelActiveExecution: () => void = () => {} const cancellation = new Promise((_resolve, reject) => { - cancelActiveExecution = () => reject(new ToolError('This browser action was cancelled.')) + cancelActiveExecution = () => { + executionController.abort() + reject(new ToolError('This browser action was cancelled.')) + } }) state.activeToolCancel = cancelActiveExecution return await session.withBrowserScope(resolvedScopeId, async () => { @@ -4629,12 +4679,14 @@ export async function executeTool( params, assertCurrentExecution, executionDeadline, - invocationEpoch + invocationEpoch, + executionController.signal ) const guardedExecution = watchdogMs === null ? execution : raceAgainstWatchdog(execution, watchdogMs, () => { + executionController.abort() if (state.toolExecutionEpoch === executionEpoch) state.toolExecutionEpoch++ if ( tool === 'browser_snapshot' || @@ -4654,6 +4706,7 @@ export async function executeTool( }) return result } finally { + executionController.abort() if (keepHiddenPageActive && !state.disposed) { session.setAutomationActive(false) } diff --git a/apps/desktop/src/main/browser-agent/page-functions.test.ts b/apps/desktop/src/main/browser-agent/page-functions.test.ts index f5b3148ba3c..d7226734454 100644 --- a/apps/desktop/src/main/browser-agent/page-functions.test.ts +++ b/apps/desktop/src/main/browser-agent/page-functions.test.ts @@ -21,6 +21,7 @@ import { readSelectElementState, scrollPage, selectOptionInElement, + setFocusedInputValue, typeIntoElement, } from '@/main/browser-agent/page-functions' @@ -132,6 +133,72 @@ afterEach(() => { document.body.innerHTML = '' }) +describe('conditional click scrolling', () => { + it('leaves a reachable target in place', () => { + const target = visible(document.createElement('button')) + document.body.append(target) + register(target) + target.scrollIntoView = vi.fn() + expect(runSerialized(clickElement, [0, false])).toMatchObject({ x: 50, y: 10 }) + expect(target.scrollIntoView).not.toHaveBeenCalled() + }) + + it('rechecks the hit target after scrolling past a sticky obstruction', () => { + const target = visible(document.createElement('button')) + const obstruction = visible(document.createElement('div')) + document.body.append(target, obstruction) + register(target) + let scrolled = false + target.scrollIntoView = vi.fn(() => { + scrolled = true + }) + Object.defineProperty(document, 'elementFromPoint', { + configurable: true, + value: () => (scrolled ? target : obstruction), + }) + expect(runSerialized(clickElement, [0, false])).toMatchObject({ x: 50, y: 10 }) + expect(target.scrollIntoView).toHaveBeenCalledOnce() + }) + + it('reveals a parent control when only its nested button is initially reachable', () => { + const card = visible(document.createElement('div')) + card.setAttribute('role', 'button') + const nested = visible(document.createElement('button')) + card.append(nested) + document.body.append(card) + register(card) + let scrolled = false + card.scrollIntoView = vi.fn(() => { + scrolled = true + }) + const nestedClick = vi.fn() + nested.addEventListener('click', nestedClick) + Object.defineProperty(document, 'elementFromPoint', { + configurable: true, + value: () => (scrolled ? card : nested), + }) + expect(runSerialized(clickElement, [0, false])).toMatchObject({ x: 50, y: 10 }) + expect(card.scrollIntoView).toHaveBeenCalledOnce() + expect(nestedClick).not.toHaveBeenCalled() + }) + + it('rejects a target removed by scrolling without dispatching input', () => { + const target = visible(document.createElement('button')) + const obstruction = visible(document.createElement('div')) + document.body.append(target, obstruction) + register(target) + const click = vi.fn() + target.addEventListener('click', click) + target.scrollIntoView = () => target.remove() + Object.defineProperty(document, 'elementFromPoint', { + configurable: true, + value: () => obstruction, + }) + expect(runSerialized(clickElement, [0])).toMatchObject({ error: 'stale' }) + expect(click).not.toHaveBeenCalled() + }) +}) + describe('serialization contract', () => { // The driver ships each of these to the page as `String(fn)`, so a reference // to anything in module scope — a shared helper, an import, a constant — @@ -692,6 +759,23 @@ describe('collectSnapshot', () => { expect(lines[0]).not.toContain('[ref=999]') }) + it('shares the text budget across inline fragments and leaves room for later controls', () => { + document.body.innerHTML = `${Array.from( + { length: 650 }, + (_, index) => `

Before ${index} inline ${index} after ${index}

` + ).join( + '' + )}${Array.from({ length: 100 }, (_, index) => ``).join('')}` + for (const element of document.querySelectorAll('*')) visible(element) + + const snapshot = collectSnapshot() as { outline: string; truncated: boolean } + expect(snapshot.truncated).toBe(true) + expect(snapshot.outline.match(/^- text /gm)).toHaveLength(120) + expect(snapshot.outline.match(/^- button /gm)).toHaveLength(100) + expect(snapshot.outline).toMatch(/button "Action 99" \[ref=\d+\]/) + expect(snapshot.outline).toMatch(/textbox "Final field" \[ref=\d+\]/) + }) + it('indexes only refs that were emitted before snapshot line truncation', () => { document.body.innerHTML = `${Array.from( { length: 599 }, @@ -733,6 +817,55 @@ describe('collectSnapshot', () => { expect(clickElement(ref)).toEqual({ error: 'file-input' }) }) + it('sets a complete multiple selection atomically and can clear it', () => { + document.body.innerHTML = + '' + const select = document.querySelector('select') as HTMLSelectElement + register(select) + const events = vi.fn() + select.addEventListener('change', events) + expect(selectOptionInElement(0, ['B', 'D'])).toEqual({ error: 'disabled' }) + expect(readSelectElementState(0)).toMatchObject({ values: ['a'] }) + expect(events).not.toHaveBeenCalled() + expect(selectOptionInElement(0, ['C', 'missing'])).toMatchObject({ error: 'no-option' }) + expect(readSelectElementState(0)).toMatchObject({ values: ['a'] }) + expect(selectOptionInElement(0, ['C', 'B'])).toMatchObject({ values: ['b', 'c'] }) + expect(readSelectElementState(0)).toMatchObject({ values: ['b', 'c'] }) + expect(events).toHaveBeenCalledOnce() + expect(selectOptionInElement(0, [])).toMatchObject({ selected: '', value: '', values: [] }) + expect(readSelectElementState(0)).toMatchObject({ values: [] }) + }) + + it('captures requested labels before event handlers replace a duplicate-value option', () => { + document.body.innerHTML = + '' + const select = document.querySelector('select') as HTMLSelectElement + register(select) + select.addEventListener('change', () => { + select.options[1].selected = false + select.options[2].selected = true + select.options[1].label = 'Rewritten' + }) + expect(selectOptionInElement(0, ['Fixed', 'Wanted'])).toMatchObject({ + values: ['fixed', 'shared'], + labels: ['Fixed', 'Wanted'], + }) + expect(readSelectElementState(0)).toMatchObject({ + values: ['fixed', 'shared'], + labels: ['Fixed', 'Other'], + }) + }) + + it('does not use multiple-selection arguments on a single-selection dropdown', () => { + document.body.innerHTML = + '' + const select = document.querySelector('select') as HTMLSelectElement + register(select) + expect(selectOptionInElement(0, ['B'])).toHaveProperty('error') + expect(select.value).toBe('a') + expect(selectOptionInElement(0, 'B')).toMatchObject({ value: 'b' }) + }) + it('keeps plain visible leaf text available as an actionable ref', () => { document.body.innerHTML = '
announce
' visible(document.querySelector('span') as HTMLSpanElement) @@ -740,6 +873,64 @@ describe('collectSnapshot', () => { expect(outlineOf(collectSnapshot())).toContain('text "announce" [ref=') }) + it('preserves mixed inline text in reading order without duplicating control labels', () => { + document.body.innerHTML = + '
Type "hello" in upper case.
' + for (const el of document.querySelectorAll('body, div, strong, button, span, b')) visible(el) + const outline = outlineOf(collectSnapshot()) + const labels = Array.from(outline.matchAll(/- text ("(?:[^"\\]|\\.)*")/g), (match) => + JSON.parse(match[1]) + ) + expect(labels).toEqual(['Type "', 'hello', '" in upper case.']) + expect(outline).toContain('button "Save draft"') + expect(outline).not.toContain('Hidden') + }) + + it('does not emit stale textarea defaults after the current value changes', () => { + document.body.innerHTML = '' + const input = visible(document.querySelector('textarea') as HTMLTextAreaElement) + input.value = 'Current draft' + expect(outlineOf(collectSnapshot())).not.toContain('Old draft') + input.value = '' + expect(outlineOf(collectSnapshot())).not.toContain('Old draft') + }) + + it('preserves direct text in open shadow roots and respects hidden hosts', () => { + document.body.innerHTML = '
' + const host = visible(document.querySelector('div') as HTMLDivElement) + const shadow = host.attachShadow({ mode: 'open' }) + shadow.innerHTML = 'Before middle after' + visible(shadow.querySelector('strong') as HTMLElement) + const outline = outlineOf(collectSnapshot()) + expect(outline.indexOf('text "Before"')).toBeLessThan(outline.indexOf('text "middle"')) + expect(outline.indexOf('text "middle"')).toBeLessThan(outline.indexOf('text "after"')) + host.hidden = true + expect(outlineOf(collectSnapshot())).not.toContain('Before') + }) + + it('gives interactive headings actionable refs while preserving static headings', () => { + document.body.innerHTML = + '

Overview

' + for (const el of document.querySelectorAll('h3, h2')) visible(el) + const clicked = vi.fn() + document.querySelector('h3')?.addEventListener('click', clicked) + const outline = outlineOf(collectSnapshot()) + expect(outline).toContain('tab "Details"') + expect(outline).toContain('aria-expanded=false') + expect(outline).toContain('heading "Overview" (h2)') + expect(clickElement(refFor(outline, 'Details'))).toMatchObject({ dispatched: true }) + expect(clicked).toHaveBeenCalledOnce() + }) + + it('exposes structured input types and multiple-selection controls', () => { + document.body.innerHTML = + '' + for (const el of document.querySelectorAll('input, select')) visible(el) + const outline = outlineOf(collectSnapshot()) + expect(outline).toContain('type="date"') + expect(outline).toMatch(/combobox "Countries" \[ref=\d+\] multiple/) + }) + it('retains sender and timestamp text omitted from a row accessibility label', () => { document.body.innerHTML = `
@@ -2042,3 +2233,91 @@ describe('describeFocusedEditable', () => { expect(describeFocusedEditable()).toEqual({ editable: true, kind: 'canvas' }) }) }) + +describe('setFocusedInputValue', () => { + for (const [type, value] of [ + ['date', '2026-09-15'], + ['time', '15:48'], + ['datetime-local', '2026-09-15T15:48'], + ['month', '2026-09'], + ['week', '2026-W38'], + ['color', '#aabbcc'], + ['range', '42'], + ]) { + it(`sets a validated ${type} value through the native setter`, () => { + document.body.innerHTML = `` + const input = visible(document.querySelector('input') as HTMLInputElement) + register(input) + input.focus() + const events: string[] = [] + input.addEventListener('input', () => events.push('input')) + input.addEventListener('change', () => events.push('change')) + expect(focusElementForTyping(0)).toMatchObject({ valueInput: true }) + expect(runSerialized(setFocusedInputValue, [0, value])).toEqual({ dispatched: true }) + expect(input.value).toBe(value) + expect(events).toEqual(['input', 'change']) + }) + } + + it('accepts native datetime normalization and bypasses an overridden value setter', () => { + document.body.innerHTML = '' + const input = document.querySelector('input') as HTMLInputElement + register(input) + input.focus() + const setter = vi.fn() + Object.defineProperty(input, 'value', { + configurable: true, + get() { + return Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.get?.call(this) + }, + set: setter, + }) + expect(setFocusedInputValue(0, '2026-09-15T15:48:00')).toEqual({ dispatched: true }) + expect(input.value).toBe('2026-09-15T15:48') + expect(setter).not.toHaveBeenCalled() + }) + + it('does not write to a newly focused input inside a registered container', () => { + document.body.innerHTML = '
' + const container = visible(document.querySelector('div') as HTMLDivElement) + visible(document.querySelector('input') as HTMLInputElement) + register(container) + expect(focusElementForTyping(0)).toMatchObject({ valueInput: true }) + const other = document.createElement('input') + other.type = 'date' + container.append(other) + other.focus() + expect(setFocusedInputValue(0, '2026-09-15')).toHaveProperty('error') + expect(other.value).toBe('') + }) + + it('rejects malformed values before changing the field or emitting events', () => { + document.body.innerHTML = '' + const input = document.querySelector('input') as HTMLInputElement + register(input) + input.focus() + const changed = vi.fn() + input.addEventListener('input', changed) + expect(setFocusedInputValue(0, '2026-02-30')).toMatchObject({ + error: expect.stringContaining('Invalid value'), + }) + expect(input.value).toBe('2026-01-01') + expect(changed).not.toHaveBeenCalled() + }) + + it('refuses changed focus, readonly fields, and credential hints', () => { + document.body.innerHTML = '' + const [input, other] = Array.from(document.querySelectorAll('input')) + register(input) + other.focus() + expect(setFocusedInputValue(0, '2026-09-15')).toEqual({ error: 'different' }) + input.focus() + input.readOnly = true + expect(setFocusedInputValue(0, '2026-09-15')).toEqual({ error: 'readonly' }) + input.readOnly = false + input.autocomplete = 'current-password' + expect(setFocusedInputValue(0, '2026-09-15')).toEqual({ error: 'password' }) + expect(input.value).toBe('') + expect(other.value).toBe('') + }) +}) diff --git a/apps/desktop/src/main/browser-agent/page-functions.ts b/apps/desktop/src/main/browser-agent/page-functions.ts index 419ae827182..f2e4ff93a4e 100644 --- a/apps/desktop/src/main/browser-agent/page-functions.ts +++ b/apps/desktop/src/main/browser-agent/page-functions.ts @@ -163,8 +163,8 @@ export function collectSnapshot(startingElementId = 0, elementId?: number): unkn const lines: string[] = [] let truncated = false let refCount = 0 - let textRefCount = 0 - const textRefCap = 120 + let textLineCount = 0 + const textLineCap = 120 let visitedNodes = 0 const previousElementId = window.__simAgentNextElementId const safePreviousElementId = @@ -480,6 +480,9 @@ export function collectSnapshot(startingElementId = 0, elementId?: number): unkn if (el.getAttribute('aria-required') === 'true') parts.push('aria-required') if (tag === 'INPUT') { const input = el as HTMLInputElement + if (!['text', 'checkbox', 'radio', 'submit', 'button', 'reset'].includes(input.type)) { + parts.push(`type=${quote(input.type)}`) + } if (input.type === 'checkbox' || input.type === 'radio') { parts.push(input.indeterminate ? 'mixed' : input.checked ? 'checked' : 'unchecked') } @@ -489,8 +492,9 @@ export function collectSnapshot(startingElementId = 0, elementId?: number): unkn const textarea = el as HTMLTextAreaElement if (textarea.readOnly) parts.push('readonly') if (textarea.required) parts.push('required') - } else if (tag === 'SELECT' && (el as HTMLSelectElement).required) { - parts.push('required') + } else if (tag === 'SELECT') { + if ((el as HTMLSelectElement).required) parts.push('required') + if ((el as HTMLSelectElement).multiple) parts.push('multiple') } for (const attribute of ['aria-checked', 'aria-expanded', 'aria-pressed', 'aria-selected']) { const value = el.getAttribute(attribute) @@ -506,7 +510,7 @@ export function collectSnapshot(startingElementId = 0, elementId?: number): unkn } const emitTextLeaf = (el: Element, indent: string, renderedLabel?: string): void => { - if (refCount >= refCap || textRefCount >= textRefCap || lines.length >= lineCap) { + if (refCount >= refCap || textLineCount >= textLineCap || lines.length >= lineCap) { truncated = true return } @@ -518,7 +522,7 @@ export function collectSnapshot(startingElementId = 0, elementId?: number): unkn ) if (!text) return const id = registerElement(el, roleFor(el), text) - textRefCount++ + textLineCount++ const lineIndex = lines.length if (push(`${indent}- text ${quote(text)} [ref=${id}]`)) refLineIndexes[id] = lineIndex } @@ -562,24 +566,47 @@ export function collectSnapshot(startingElementId = 0, elementId?: number): unkn ) } - const walk = (elements: Iterable, depth: number, suppressTextCoveredBy = ''): void => { + const walk = (nodes: Iterable, depth: number, suppressTextCoveredBy = ''): void => { if (refCount >= refCap || depth > depthCap) { truncated = true return } - for (const el of elements) { + for (const node of nodes) { visitedNodes++ if (refCount >= refCap || visitedNodes > nodeCap) { truncated = true return } + const indent = ' '.repeat(depth) + if (node.nodeType === Node.TEXT_NODE) { + const root = node.getRootNode() + const parent = node.parentElement ?? ('host' in root ? (root.host as Element) : null) + if (parent?.tagName.toUpperCase() === 'TEXTAREA') continue + const text = cut((node.textContent || '').replace(/\s+/g, ' ').trim(), 160) + if ( + text && + parent && + isVisible(parent) && + (!suppressTextCoveredBy || !suppressTextCoveredBy.includes(text)) + ) { + if (textLineCount >= textLineCap) { + truncated = true + continue + } + if (!push(`${indent}- text ${quote(text)}`)) return + textLineCount++ + } + continue + } + if (node.nodeType !== Node.ELEMENT_NODE) continue + const el = node as Element const tag = String(el.tagName || '').toUpperCase() if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'NOSCRIPT' || tag === 'TEMPLATE') continue - const indent = ' '.repeat(depth) let childDepth = depth let emittedInteractive = false let interactiveName = '' + let emittedText = '' const visible = isVisible(el) if (el.matches(landmarkSelector) && visible) { @@ -587,15 +614,15 @@ export function collectSnapshot(startingElementId = 0, elementId?: number): unkn childDepth = depth + 1 } else { const level = headingLevel(el) - if (level !== null && visible) { - const text = cut(((el as HTMLElement).innerText || '').replace(/\s+/g, ' ').trim(), 160) - if (text) push(`${indent}- heading ${quote(text)} (h${level})`) - } else if (visible && (el.matches(interactiveSelector) || pointerBoundary(el))) { + if (visible && (el.matches(interactiveSelector) || pointerBoundary(el))) { emitInteractive(el, indent) emittedInteractive = true interactiveName = nameFor(el) // Interactive containers rarely nest other interactives; still // recurse so e.g. a clickable card exposes its inner links. + } else if (level !== null && visible) { + emittedText = cut(((el as HTMLElement).innerText || '').replace(/\s+/g, ' ').trim(), 160) + if (emittedText) push(`${indent}- heading ${quote(emittedText)} (h${level})`) } else if (visible) { const visibleElementChild = Array.from(el.children).some(isVisible) const leafLabel = visibleElementChild @@ -609,18 +636,21 @@ export function collectSnapshot(startingElementId = 0, elementId?: number): unkn (!suppressTextCoveredBy || !suppressTextCoveredBy.includes(leafLabel)) ) { emitTextLeaf(el, indent, leafLabel) + emittedText = leafLabel } } } - const coveredText = emittedInteractive ? interactiveName : suppressTextCoveredBy + const coveredText = emittedInteractive + ? interactiveName + : emittedText || suppressTextCoveredBy if (tag === 'IFRAME' || tag === 'FRAME') { try { const innerDoc = (el as HTMLIFrameElement).contentDocument if (innerDoc?.body && isVisible(el)) { if (!push(`${indent}- iframe:`)) return - walk(innerDoc.body.children, childDepth + 1, coveredText) + walk(innerDoc.body.childNodes, childDepth + 1, coveredText) } else if (scopedRoot && !innerDoc && visible) { truncated = true } @@ -631,13 +661,13 @@ export function collectSnapshot(startingElementId = 0, elementId?: number): unkn } const shadow = (el as HTMLElement).shadowRoot - if (shadow) walk(shadow.children, childDepth, coveredText) - walk(el.children, childDepth, coveredText) + if (shadow) walk(shadow.childNodes, childDepth, coveredText) + walk(el.childNodes, childDepth, coveredText) } } if (scopedRoot) walk([scopedRoot], 0) - else if (document.body) walk(document.body.children, 0) + else if (document.body) walk(document.body.childNodes, 0) /** * React commonly replaces a control's DOM node while preserving its @@ -929,7 +959,8 @@ export function clickElement( id: number, dispatchSynthetic = true, focusForKeyboard = false, - allowDisabled = false + allowDisabled = false, + scrollToTarget = false ): unknown { const isSecretField = (node: Element | null): boolean => { if (!node || String(node.tagName || '').toUpperCase() !== 'INPUT') return false @@ -974,7 +1005,10 @@ export function clickElement( return { error: 'file-input' } } } - el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' }) + if (scrollToTarget) { + el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' }) + if (!el.isConnected) return { error: 'stale', reason: window.__simAgentStaleReason } + } const view = el.ownerDocument.defaultView if (!view) return { error: 'stale', reason: window.__simAgentStaleReason } @@ -1021,7 +1055,11 @@ export function clickElement( rect.right - rect.left > 1 && rect.bottom - rect.top > 1 ) - if (rects.length === 0) return { error: 'not-visible' } + if (rects.length === 0) { + return scrollToTarget + ? { error: 'not-visible' } + : clickElement(id, dispatchSynthetic, focusForKeyboard, allowDisabled, true) + } const composedParent = (node: Element): Element | null => { if (node.parentElement) return node.parentElement @@ -1202,6 +1240,9 @@ export function clickElement( if (suggestionsCoverFocusedEditable()) { return { error: 'suggestions-open', blocker: blockerLabel(blocker) } } + if (!scrollToTarget) { + return clickElement(id, dispatchSynthetic, focusForKeyboard, allowDisabled, true) + } // A hit INSIDE the requested element is not an overlay — it is the ref // wrapping its own control (a row containing a button, a card containing a // link). hitBelongsToTarget rejects both cases identically, so this was @@ -1247,6 +1288,9 @@ export function clickElement( if (parentElementAt) { const parentHit: Element | null = parentElementAt(pageX, pageY) if (parentHit !== frame) { + if (!scrollToTarget) { + return clickElement(id, dispatchSynthetic, focusForKeyboard, allowDisabled, true) + } return { error: 'obstructed', blocker: blockerLabel(parentHit) } } } @@ -1340,6 +1384,7 @@ export function focusElementForTyping(id: number, moveFocus = true): unknown { .some((token) => token === 'current-password' || token === 'new-password') } + const valueInputTypes = ['date', 'time', 'datetime-local', 'month', 'week', 'color', 'range'] const resolver = window.__simAgentResolveElement const resolved = resolver?.(id) const el = resolver ? resolved?.element : (window.__simAgentElements || [])[id] @@ -1352,7 +1397,7 @@ export function focusElementForTyping(id: number, moveFocus = true): unknown { if (field.readOnly || field.getAttribute('aria-readonly') === 'true') return 'readonly' if (String(field.tagName || '').toUpperCase() === 'TEXTAREA') return 'writable' const type = String((field as HTMLInputElement).type || 'text').toLowerCase() - return ['text', 'search', 'email', 'url', 'tel', 'number'].includes(type) + return ['text', 'search', 'email', 'url', 'tel', 'number', ...valueInputTypes].includes(type) ? 'writable' : 'not-editable' } @@ -1366,7 +1411,16 @@ export function focusElementForTyping(id: number, moveFocus = true): unknown { if ( tag === 'TEXTAREA' || (tag === 'INPUT' && - ['text', 'search', 'email', 'url', 'tel', 'number', 'password'].includes(inputType)) || + [ + 'text', + 'search', + 'email', + 'url', + 'tel', + 'number', + 'password', + ...valueInputTypes, + ].includes(inputType)) || (node as HTMLElement).isContentEditable || // An ARIA-only textbox. The snapshot already advertises these as // `[textbox]` with a ref, and browser_insert_text accepts them, so @@ -1628,10 +1682,67 @@ export function focusElementForTyping(id: number, moveFocus = true): unknown { x: chosenPoint.x, y: chosenPoint.y, coveredByRelatedPopup, + valueInput: + editableTag === 'INPUT' && valueInputTypes.includes((editable as HTMLInputElement).type), refRecovered: resolved?.recovered === true, } } +/** Sets structured native inputs after the driver's ordinary typing actionability checks. */ +export function setFocusedInputValue(id: number, text: string): unknown { + const resolver = window.__simAgentResolveElement + const resolved = resolver?.(id) + const registered = resolver ? resolved?.element : (window.__simAgentElements || [])[id] + if (!registered?.isConnected) return { error: 'stale', reason: window.__simAgentStaleReason } + let active = registered.ownerDocument.activeElement + while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement + if (String(registered.tagName || '').toUpperCase() !== 'INPUT') { + return { + error: + 'Structured inputs require the field reference itself, not a container. Take a fresh browser_snapshot.', + } + } + if (active !== registered) return { error: 'different' } + const input = active as HTMLInputElement + const type = input.type.toLowerCase() + const hints = (input.getAttribute('autocomplete') || '').toLowerCase().split(/\s+/) + if ( + type === 'password' || + hints.some((hint) => hint === 'current-password' || hint === 'new-password') + ) { + return { error: 'password' } + } + if (!['date', 'time', 'datetime-local', 'month', 'week', 'color', 'range'].includes(type)) { + return { + error: + 'The focused field no longer accepts a structured input value. Take a fresh browser_snapshot.', + } + } + if (input.matches(':disabled') || input.getAttribute('aria-disabled') === 'true') + return { error: 'disabled' } + if (input.readOnly || input.getAttribute('aria-readonly') === 'true') return { error: 'readonly' } + const value = type === 'color' ? text.trim().toLowerCase() : text.trim() + const probe = input.cloneNode(false) as HTMLInputElement + probe.value = value + if ( + (value !== '' && probe.value === '') || + (['color', 'range'].includes(type) && probe.value !== value) + ) { + return { + error: `Invalid value for input[type=${type}]. Use the native format; the field was not changed.`, + } + } + const view = input.ownerDocument.defaultView + if (!view) return { error: 'stale' } + const setter = Object.getOwnPropertyDescriptor(view.HTMLInputElement.prototype, 'value')?.set + if (!setter) + return { error: 'The native input value setter is unavailable; the field was not changed.' } + setter.call(input, probe.value) + input.dispatchEvent(new view.Event('input', { bubbles: true, composed: true })) + input.dispatchEvent(new view.Event('change', { bubbles: true })) + return { dispatched: true } +} + /** * Reads back the focused element's state after a native key/type action so * the driver can report what actually happened instead of assuming success. @@ -2657,42 +2768,74 @@ export function scrollPage(direction: string, amount?: number, elementId?: numbe } } -export function selectOptionInElement(id: number, value: string): unknown { +export function selectOptionInElement(id: number, value: string | string[]): unknown { const resolver = window.__simAgentResolveElement const resolved = resolver?.(id) const el = resolver ? resolved?.element : (window.__simAgentElements || [])[id] if (!el || !el.isConnected) return { error: 'stale', reason: window.__simAgentStaleReason } if (String(el.tagName || '').toUpperCase() !== 'SELECT') return { error: 'not-select' } const select = el as HTMLSelectElement - if (select.disabled || select.getAttribute('aria-disabled') === 'true') { + if (select.matches(':disabled') || select.getAttribute('aria-disabled') === 'true') { return { error: 'disabled' } } - const wanted = value.trim().toLowerCase() - const option = Array.from(select.options).find( - (o) => o.value.trim().toLowerCase() === wanted || o.label.trim().toLowerCase() === wanted - ) - if (!option) { + if (Array.isArray(value) && !select.multiple) { return { - error: 'no-option', - options: Array.from(select.options) - .slice(0, 50) - .map((o) => - o.label + error: + 'Use value for a single-selection dropdown; values requires a multiple-selection control.', + } + } + const requested = Array.isArray(value) ? value : [value] + if (requested.length > 100 || requested.some((entry) => typeof entry !== 'string')) { + return { error: 'A selection requires at most 100 string values.' } + } + const options = Array.from(select.options) + const chosen = new Set() + for (const entry of requested) { + const wanted = entry.trim().toLowerCase() + const option = options.find( + (candidate) => + candidate.value.trim().toLowerCase() === wanted || + candidate.label.trim().toLowerCase() === wanted + ) + if (!option) { + return { + error: 'no-option', + options: options.slice(0, 50).map((candidate) => + candidate.label .trim() .slice(0, 200) .replace(/[\uD800-\uDBFF]$/, '') ), + } } + if ( + option.disabled || + (option.parentElement as HTMLOptGroupElement | null)?.disabled === true + ) { + return { error: 'disabled' } + } + chosen.add(option) } - if (option.disabled || (option.parentElement as HTMLOptGroupElement | null)?.disabled === true) { - return { error: 'disabled' } + const selected = options.filter((option) => chosen.has(option)) + const selection = { + selected: selected[0]?.label.trim() || '', + value: selected[0]?.value || '', + ...(select.multiple + ? { + values: selected.map((option) => option.value), + labels: selected.map((option) => option.label.trim()), + } + : {}), + } + if (select.multiple) { + for (const option of options) option.selected = chosen.has(option) + } else { + select.value = selected[0].value } - select.value = option.value select.dispatchEvent(new Event('input', { bubbles: true })) select.dispatchEvent(new Event('change', { bubbles: true })) return { - selected: option.label.trim(), - value: option.value, + ...selection, refRecovered: resolved?.recovered === true, } } @@ -2804,9 +2947,19 @@ export function readSelectElementState(id: number): unknown { if (!el || !el.isConnected) return { error: 'stale', reason: window.__simAgentStaleReason } if (String(el.tagName || '').toUpperCase() !== 'SELECT') return { error: 'not-select' } const select = el as HTMLSelectElement + const values: string[] = [] + const labels: string[] = [] + if (select.multiple) { + for (const option of select.selectedOptions) { + values.push(option.value) + labels.push(option.label.trim()) + if (values.length > 100) break + } + } return { selected: select.selectedOptions[0]?.label.trim() || '', value: select.value, + ...(select.multiple ? { values, labels } : {}), } } diff --git a/apps/sim/lib/copilot/generated/tool-catalog-v1.ts b/apps/sim/lib/copilot/generated/tool-catalog-v1.ts index dbf390a7c80..d82e0066cb7 100644 --- a/apps/sim/lib/copilot/generated/tool-catalog-v1.ts +++ b/apps/sim/lib/copilot/generated/tool-catalog-v1.ts @@ -1626,16 +1626,27 @@ export const BrowserSelectOption: ToolCatalogEntry = { route: 'client', mode: 'async', parameters: { - type: 'object', + oneOf: [{ required: ['value'] }, { required: ['values'] }], properties: { elementId: { - type: 'number', description: "The element id to act on (from the current tab's most recent browser_snapshot). Treat refs as invalid across tab switches or later snapshots.", + type: 'number', + }, + value: { + description: "One option's visible label or value. Omit when supplying values.", + type: 'string', + }, + values: { + description: + 'The complete desired selection for a native multiple-selection control: at most 100 visible labels or values. Empty array clears the selection. Omit value when using this field.', + items: { type: 'string' }, + maxItems: 100, + type: 'array', }, - value: { type: 'string', description: "The option's visible label or its value." }, }, - required: ['elementId', 'value'], + required: ['elementId'], + type: 'object', }, resultSchema: { type: 'object', @@ -1644,6 +1655,12 @@ export const BrowserSelectOption: ToolCatalogEntry = { type: 'boolean', description: 'Whether the settled readback retained the requested selection.', }, + labels: { + type: 'array', + description: + 'Visible labels for the complete selected set in a multiple-selection control, in option order.', + items: { type: 'string' }, + }, note: { type: 'string', description: 'Guidance when the page reverted the selection.' }, notices: { type: 'array', @@ -1655,8 +1672,20 @@ export const BrowserSelectOption: ToolCatalogEntry = { type: 'object', description: 'Settled selected label and value.', properties: { + labels: { + type: 'array', + description: + 'Visible labels for the complete selected set in a multiple-selection control, in option order.', + items: { type: 'string' }, + }, selected: { type: 'string', description: 'Settled visible option label.' }, value: { type: 'string', description: 'Settled option value.' }, + values: { + type: 'array', + description: + 'Selected native option values in DOM order; included for multiple-selection controls.', + items: { type: 'string' }, + }, }, }, refRecovered: { @@ -1666,6 +1695,12 @@ export const BrowserSelectOption: ToolCatalogEntry = { }, selected: { type: 'string', description: 'Canonical visible label of the matched option.' }, value: { type: 'string', description: 'Canonical value of the matched option.' }, + values: { + type: 'array', + description: + 'Selected native option values in DOM order; included for multiple-selection controls.', + items: { type: 'string' }, + }, }, required: ['selected'], }, @@ -1818,7 +1853,7 @@ export const BrowserType: ToolCatalogEntry = { text: { type: 'string', description: - "The text to type. Replaces the element's current content. Must be non-empty — an empty string is rejected as a missing parameter; to clear a field, press Mod+A then Backspace with browser_press_key.", + 'The replacement value. Empty text clears an ordinary text field. For structured inputs use YYYY-MM-DD (date), HH:mm (time), YYYY-MM-DDTHH:mm (datetime-local), YYYY-MM (month), YYYY-Www (week), #rrggbb (color), or a numeric range value. Alternatively use Mod+A then Backspace to clear ordinary text with browser_press_key.', }, }, required: ['elementId', 'text'], diff --git a/apps/sim/lib/copilot/generated/tool-schemas-v1.ts b/apps/sim/lib/copilot/generated/tool-schemas-v1.ts index cb977c36ed5..a1b5c10ecb9 100644 --- a/apps/sim/lib/copilot/generated/tool-schemas-v1.ts +++ b/apps/sim/lib/copilot/generated/tool-schemas-v1.ts @@ -1540,19 +1540,36 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { }, browser_select_option: { parameters: { - type: 'object', + oneOf: [ + { + required: ['value'], + }, + { + required: ['values'], + }, + ], properties: { elementId: { - type: 'number', description: "The element id to act on (from the current tab's most recent browser_snapshot). Treat refs as invalid across tab switches or later snapshots.", + type: 'number', }, value: { + description: "One option's visible label or value. Omit when supplying values.", type: 'string', - description: "The option's visible label or its value.", + }, + values: { + description: + 'The complete desired selection for a native multiple-selection control: at most 100 visible labels or values. Empty array clears the selection. Omit value when using this field.', + items: { + type: 'string', + }, + maxItems: 100, + type: 'array', }, }, - required: ['elementId', 'value'], + required: ['elementId'], + type: 'object', }, resultSchema: { type: 'object', @@ -1561,6 +1578,14 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { type: 'boolean', description: 'Whether the settled readback retained the requested selection.', }, + labels: { + type: 'array', + description: + 'Visible labels for the complete selected set in a multiple-selection control, in option order.', + items: { + type: 'string', + }, + }, note: { type: 'string', description: 'Guidance when the page reverted the selection.', @@ -1577,6 +1602,14 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { type: 'object', description: 'Settled selected label and value.', properties: { + labels: { + type: 'array', + description: + 'Visible labels for the complete selected set in a multiple-selection control, in option order.', + items: { + type: 'string', + }, + }, selected: { type: 'string', description: 'Settled visible option label.', @@ -1585,6 +1618,14 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { type: 'string', description: 'Settled option value.', }, + values: { + type: 'array', + description: + 'Selected native option values in DOM order; included for multiple-selection controls.', + items: { + type: 'string', + }, + }, }, }, refRecovered: { @@ -1600,6 +1641,14 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { type: 'string', description: 'Canonical value of the matched option.', }, + values: { + type: 'array', + description: + 'Selected native option values in DOM order; included for multiple-selection controls.', + items: { + type: 'string', + }, + }, }, required: ['selected'], }, @@ -1769,7 +1818,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { text: { type: 'string', description: - "The text to type. Replaces the element's current content. Must be non-empty — an empty string is rejected as a missing parameter; to clear a field, press Mod+A then Backspace with browser_press_key.", + 'The replacement value. Empty text clears an ordinary text field. For structured inputs use YYYY-MM-DD (date), HH:mm (time), YYYY-MM-DDTHH:mm (datetime-local), YYYY-MM (month), YYYY-Www (week), #rrggbb (color), or a numeric range value. Alternatively use Mod+A then Backspace to clear ordinary text with browser_press_key.', }, }, required: ['elementId', 'text'], diff --git a/apps/sim/lib/copilot/tools/client/browser-tool-execution.test.ts b/apps/sim/lib/copilot/tools/client/browser-tool-execution.test.ts index 26ac1d9200a..1fdb9165092 100644 --- a/apps/sim/lib/copilot/tools/client/browser-tool-execution.test.ts +++ b/apps/sim/lib/copilot/tools/client/browser-tool-execution.test.ts @@ -581,6 +581,25 @@ describe('executeBrowserToolOnClient', () => { } ) + it('reports an unconfirmed effect without retrying or marking completed input as failed', async () => { + const result = { dispatched: true, effectObserved: false, possibleEffectObserved: true } + mockExecuteBrowserTool.mockResolvedValue(result) + const toolCallId = nextToolCallId() + + executeBrowserToolOnClient(toolCallId, 'browser_click', { elementId: 1 }) + await flush() + executeBrowserToolOnClient(toolCallId, 'browser_click', { elementId: 1 }) + await flush() + + expect(mockExecuteBrowserTool).toHaveBeenCalledOnce() + expect(mockReportCompletion).toHaveBeenCalledWith( + toolCallId, + 'success', + 'Browser input completed; its effect is unconfirmed. Inspect the current state before retrying.', + result + ) + }) + it('uses unload-safe delivery when a stateful replay-guard rejection cannot be reported normally', async () => { const storageWrite = vi.spyOn(window.sessionStorage, 'setItem').mockImplementation(() => { throw new DOMException('Quota exceeded', 'QuotaExceededError') diff --git a/apps/sim/lib/copilot/tools/client/browser-tool-execution.ts b/apps/sim/lib/copilot/tools/client/browser-tool-execution.ts index c9b52d8b139..ced31164d4f 100644 --- a/apps/sim/lib/copilot/tools/client/browser-tool-execution.ts +++ b/apps/sim/lib/copilot/tools/client/browser-tool-execution.ts @@ -984,6 +984,7 @@ async function doExecuteBrowserTool( } nativeActionPending = false if (cancelled) return + const effectUnconfirmed = isRecordLike(result) && result.effectObserved === false const formStopped = toolName === 'browser_fill_form' && isRecordLike(result) && result.completed === false reportTerminalCompletion( @@ -993,7 +994,9 @@ async function doExecuteBrowserTool( : ASYNC_TOOL_CONFIRMATION_STATUS.success, message: formStopped ? 'Form filling stopped; inspect the partial result' - : 'Browser action completed', + : effectUnconfirmed + ? 'Browser input completed; its effect is unconfirmed. Inspect the current state before retrying.' + : 'Browser action completed', data: sanitizeResultForModel(toolName, result), }, 'Failed to report successful browser tool completion' diff --git a/apps/sim/lib/copilot/tools/server/generated-schema.test.ts b/apps/sim/lib/copilot/tools/server/generated-schema.test.ts index 1df1ba9e75a..3dc3af8aabb 100644 --- a/apps/sim/lib/copilot/tools/server/generated-schema.test.ts +++ b/apps/sim/lib/copilot/tools/server/generated-schema.test.ts @@ -5,6 +5,30 @@ import { describe, expect, it } from 'vitest' import { validateGeneratedToolPayload } from '@/lib/copilot/tools/server/generated-schema' import { OrchestrationError } from '@/lib/core/orchestration/types' +describe('validateGeneratedToolPayload browser_select_option parameters', () => { + it.each([ + { elementId: 0, value: 'a' }, + { elementId: 0, values: ['a', 'b'] }, + { elementId: 0, values: [] }, + ])('accepts a single selection mode %#', (payload) => { + expect(validateGeneratedToolPayload('browser_select_option', 'parameters', payload)).toBe( + payload + ) + }) + + it.each([ + { elementId: 0 }, + { elementId: 0, value: 'a', values: ['b'] }, + { elementId: 0, value: 'a', values: [] }, + { elementId: 0, values: [1] }, + { elementId: 0, values: Array.from({ length: 101 }, () => 'a') }, + ])('rejects missing, conflicting or malformed selection arguments %#', (payload) => { + expect(() => + validateGeneratedToolPayload('browser_select_option', 'parameters', payload) + ).toThrow(OrchestrationError) + }) +}) + describe('validateGeneratedToolPayload browser_fill_form parameters', () => { it('accepts mixed fields, including empty text and false checked state', () => { const payload = { From 83c165a907310623bc489035b9a2fca29c902046 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 15 Sep 2026 19:13:41 -0700 Subject: [PATCH 03/43] feat(access-requests): request and review permission access (#7871) * feat(access-requests): request and review permission access * fix(access-requests): reuse resource states and harden review lifecycle * fix(access-requests): recheck rollout and retain public models --- apps/sim/.env.example | 1 + apps/sim/app/access-requests/layout.tsx | 15 + apps/sim/app/access-requests/loading.tsx | 5 + apps/sim/app/access-requests/page.tsx | 70 + .../[requestId]/cancel/route.ts | 23 + .../api/access-requests/discovery/route.ts | 22 + apps/sim/app/api/access-requests/route.ts | 47 + .../[requestId]/preview/route.ts | 22 + .../[requestId]/resolve/route.ts | 27 + .../[id]/access-requests/route.ts | 22 + .../[id]/access-requests/settings/route.ts | 41 + .../app/api/webhooks/outbox/process/route.ts | 2 + .../[workspaceId]/access-requests/loading.tsx | 5 + .../[workspaceId]/access-requests/page.tsx | 19 + .../files-empty-state.tsx | 32 +- .../knowledge-empty-state.tsx | 35 +- .../tables-empty-state.tsx | 33 +- .../workspace/[workspaceId]/files/files.tsx | 9 + .../workspace/[workspaceId]/files/prefetch.ts | 3 + .../components/special-tags/special-tags.tsx | 16 +- .../usage-upgrade-display.test.tsx | 67 + .../app/workspace/[workspaceId]/home/home.tsx | 11 +- .../integrations/[block]/page.tsx | 9 +- .../connected/[credentialId]/page.tsx | 7 +- .../integrations/integrations.tsx | 9 + .../knowledge/[id]/[documentId]/page.tsx | 15 +- .../[workspaceId]/knowledge/[id]/page.tsx | 5 +- .../knowledge/knowledge.test.tsx | 6 + .../[workspaceId]/knowledge/knowledge.tsx | 9 + .../[workspaceId]/knowledge/prefetch.ts | 7 +- .../lib/authorize-resource-prefetch.ts | 28 + .../[workspaceId]/lib/prefetch.test.ts | 37 +- .../settings/[section]/page.test.tsx | 19 + .../[workspaceId]/settings/[section]/page.tsx | 16 + .../settings/[section]/settings.tsx | 14 +- .../[workspaceId]/tables/[tableId]/page.tsx | 5 +- .../[workspaceId]/tables/prefetch.ts | 3 + .../workspace/[workspaceId]/tables/tables.tsx | 9 + .../panel/components/toolbar/toolbar.test.tsx | 207 + .../panel/components/toolbar/toolbar.tsx | 137 +- .../w/[workflowId]/components/panel/panel.tsx | 40 +- .../search-modal/search-modal.test.tsx | 4 + .../components/search-modal/search-modal.tsx | 12 +- .../settings-sidebar/settings-sidebar.tsx | 38 +- .../sidebar-nav-chip/sidebar-nav-chip.tsx | 4 + .../workspace-header.test.tsx | 11 + .../workspace-header/workspace-header.tsx | 29 +- .../w/components/sidebar/sidebar.tsx | 37 +- .../access-request-review.test.tsx | 181 + .../access-requests/access-request-review.tsx | 248 + .../access-requests-loading.tsx | 12 + .../member-limit-request-action.tsx | 29 + .../my-access-request-details.tsx | 111 + .../my-access-requests.test.tsx | 108 + .../access-requests/my-access-requests.tsx | 204 + .../organization-access-requests.tsx | 153 + .../permission-access-boundary.test.tsx | 161 + .../permission-access-boundary.tsx | 123 + .../access-requests/policy-changes.test.ts | 110 + .../access-requests/policy-changes.tsx | 111 + .../request-access-action.test.tsx | 60 + .../access-requests/request-access-action.tsx | 171 + .../access-requests/search-params.test.ts | 24 + .../access-requests/search-params.ts | 59 + apps/sim/components/access-requests/status.ts | 9 + .../components/emails/notifications/index.ts | 1 + .../permission-access-request-email.tsx | 39 + .../emails/render-notifications.test.ts | 18 + apps/sim/components/emails/render.ts | 8 + apps/sim/components/emails/subjects.ts | 6 + .../components/empty-state/empty-state.tsx | 2 +- apps/sim/components/settings/navigation.ts | 7 +- .../components/access-control.test.tsx | 9 +- .../components/access-control.tsx | 35 +- .../hooks/queries/access-requests.test.tsx | 315 + apps/sim/hooks/queries/access-requests.ts | 242 + apps/sim/hooks/use-permission-config.test.tsx | 35 +- apps/sim/hooks/use-permission-config.ts | 34 + .../lib/api/contracts/access-requests.test.ts | 133 + apps/sim/lib/api/contracts/access-requests.ts | 312 + .../organization-authorization.test.ts | 43 + .../application/organization-authorization.ts | 33 +- apps/sim/lib/core/config/env.ts | 1 + .../sim/lib/core/config/feature-flags.test.ts | 26 +- apps/sim/lib/core/config/feature-flags.ts | 5 + .../lib/permission-access-requests/README.md | 32 + .../application/authorization.test.ts | 235 + .../application/authorization.ts | 152 + .../application/authorized-use-case.test.ts | 219 + .../application/authorized-use-case.ts | 113 + .../application/operations.ts | 66 + .../application/prepare.ts | 37 + .../application/requests.test.ts | 525 + .../application/requests.ts | 565 + .../application/review.test.ts | 526 + .../application/review.ts | 397 + .../catalog-registry.ts | 158 + .../catalog.test.ts | 259 + .../lib/permission-access-requests/catalog.ts | 82 + .../permission-access-requests/constants.ts | 4 + .../impact.postgres.test.ts | 103 + .../lib/permission-access-requests/impact.ts | 136 + .../notification-events.ts | 2 + .../notifications.test.ts | 388 + .../notifications.ts | 210 + .../lib/permission-access-requests/policy.ts | 122 + .../permission-access-requests/repository.ts | 117 + .../lib/permission-access-requests/schemas.ts | 56 + .../settings.test.ts | 95 + .../permission-access-requests/settings.ts | 26 + .../lib/permission-access-requests/types.ts | 82 + .../access-requests/targets.test.ts | 333 + .../access-requests/targets.ts | 446 + .../lib/permission-groups/resolve.server.ts | 9 +- .../workspace-section-access.test.ts | 56 + .../application/workspace-section-access.ts | 44 +- .../sim/lib/table/application/context.test.ts | 2 +- apps/sim/lib/table/application/context.ts | 3 +- apps/sim/lib/table/application/tables.test.ts | 4 +- apps/sim/lib/table/application/tables.ts | 29 +- apps/sim/providers/models.test.ts | 32 + apps/sim/providers/models.ts | 12 +- packages/audit/src/types.ts | 7 + .../0349_permission_access_requests.sql | 38 + .../db/migrations/meta/0349_snapshot.json | 27023 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + ...access-requests-migration.postgres.test.ts | 115 + packages/db/schema.ts | 64 + packages/testing/src/mocks/audit.mock.ts | 7 + packages/testing/src/mocks/schema.mock.ts | 37 + ...check-tool-registry-boundary.baseline.json | 132 +- 131 files changed, 37321 insertions(+), 208 deletions(-) create mode 100644 apps/sim/app/access-requests/layout.tsx create mode 100644 apps/sim/app/access-requests/loading.tsx create mode 100644 apps/sim/app/access-requests/page.tsx create mode 100644 apps/sim/app/api/access-requests/[requestId]/cancel/route.ts create mode 100644 apps/sim/app/api/access-requests/discovery/route.ts create mode 100644 apps/sim/app/api/access-requests/route.ts create mode 100644 apps/sim/app/api/organizations/[id]/access-requests/[requestId]/preview/route.ts create mode 100644 apps/sim/app/api/organizations/[id]/access-requests/[requestId]/resolve/route.ts create mode 100644 apps/sim/app/api/organizations/[id]/access-requests/route.ts create mode 100644 apps/sim/app/api/organizations/[id]/access-requests/settings/route.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/access-requests/loading.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/access-requests/page.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/usage-upgrade-display.test.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/lib/authorize-resource-prefetch.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.test.tsx create mode 100644 apps/sim/components/access-requests/access-request-review.test.tsx create mode 100644 apps/sim/components/access-requests/access-request-review.tsx create mode 100644 apps/sim/components/access-requests/access-requests-loading.tsx create mode 100644 apps/sim/components/access-requests/member-limit-request-action.tsx create mode 100644 apps/sim/components/access-requests/my-access-request-details.tsx create mode 100644 apps/sim/components/access-requests/my-access-requests.test.tsx create mode 100644 apps/sim/components/access-requests/my-access-requests.tsx create mode 100644 apps/sim/components/access-requests/organization-access-requests.tsx create mode 100644 apps/sim/components/access-requests/permission-access-boundary.test.tsx create mode 100644 apps/sim/components/access-requests/permission-access-boundary.tsx create mode 100644 apps/sim/components/access-requests/policy-changes.test.ts create mode 100644 apps/sim/components/access-requests/policy-changes.tsx create mode 100644 apps/sim/components/access-requests/request-access-action.test.tsx create mode 100644 apps/sim/components/access-requests/request-access-action.tsx create mode 100644 apps/sim/components/access-requests/search-params.test.ts create mode 100644 apps/sim/components/access-requests/search-params.ts create mode 100644 apps/sim/components/access-requests/status.ts create mode 100644 apps/sim/components/emails/notifications/permission-access-request-email.tsx create mode 100644 apps/sim/hooks/queries/access-requests.test.tsx create mode 100644 apps/sim/hooks/queries/access-requests.ts create mode 100644 apps/sim/lib/api/contracts/access-requests.test.ts create mode 100644 apps/sim/lib/api/contracts/access-requests.ts create mode 100644 apps/sim/lib/permission-access-requests/README.md create mode 100644 apps/sim/lib/permission-access-requests/application/authorization.test.ts create mode 100644 apps/sim/lib/permission-access-requests/application/authorization.ts create mode 100644 apps/sim/lib/permission-access-requests/application/authorized-use-case.test.ts create mode 100644 apps/sim/lib/permission-access-requests/application/authorized-use-case.ts create mode 100644 apps/sim/lib/permission-access-requests/application/operations.ts create mode 100644 apps/sim/lib/permission-access-requests/application/prepare.ts create mode 100644 apps/sim/lib/permission-access-requests/application/requests.test.ts create mode 100644 apps/sim/lib/permission-access-requests/application/requests.ts create mode 100644 apps/sim/lib/permission-access-requests/application/review.test.ts create mode 100644 apps/sim/lib/permission-access-requests/application/review.ts create mode 100644 apps/sim/lib/permission-access-requests/catalog-registry.ts create mode 100644 apps/sim/lib/permission-access-requests/catalog.test.ts create mode 100644 apps/sim/lib/permission-access-requests/catalog.ts create mode 100644 apps/sim/lib/permission-access-requests/constants.ts create mode 100644 apps/sim/lib/permission-access-requests/impact.postgres.test.ts create mode 100644 apps/sim/lib/permission-access-requests/impact.ts create mode 100644 apps/sim/lib/permission-access-requests/notification-events.ts create mode 100644 apps/sim/lib/permission-access-requests/notifications.test.ts create mode 100644 apps/sim/lib/permission-access-requests/notifications.ts create mode 100644 apps/sim/lib/permission-access-requests/policy.ts create mode 100644 apps/sim/lib/permission-access-requests/repository.ts create mode 100644 apps/sim/lib/permission-access-requests/schemas.ts create mode 100644 apps/sim/lib/permission-access-requests/settings.test.ts create mode 100644 apps/sim/lib/permission-access-requests/settings.ts create mode 100644 apps/sim/lib/permission-access-requests/types.ts create mode 100644 apps/sim/lib/permission-groups/access-requests/targets.test.ts create mode 100644 apps/sim/lib/permission-groups/access-requests/targets.ts create mode 100644 packages/db/migrations/0349_permission_access_requests.sql create mode 100644 packages/db/migrations/meta/0349_snapshot.json create mode 100644 packages/db/permission-access-requests-migration.postgres.test.ts diff --git a/apps/sim/.env.example b/apps/sim/.env.example index c8b8583d103..f5d05f0ac23 100644 --- a/apps/sim/.env.example +++ b/apps/sim/.env.example @@ -214,6 +214,7 @@ CRON_SECRET=your_cron_secret # Use `openssl rand -hex 32` to generate. Authentic # FORKING_ENABLED= # Workspace forks # CREDENTIAL_GROUPS= # Enterprise managed OAuth collections # TABLE_ROW_TTL= # Table TTL columns and expired-row cleanup +# PERMISSION_ACCESS_REQUESTS_ENABLED= # Global access-request rollout; organizations may opt out # KNOWLEDGE_MEMBER_ACCESS= # Per-member knowledge connectors and hybrid-by-default retrieval # ORGANIZATIONS_ENABLED= / NEXT_PUBLIC_ORGANIZATIONS_ENABLED= # Organizations only diff --git a/apps/sim/app/access-requests/layout.tsx b/apps/sim/app/access-requests/layout.tsx new file mode 100644 index 00000000000..f4b12888e24 --- /dev/null +++ b/apps/sim/app/access-requests/layout.tsx @@ -0,0 +1,15 @@ +import type { ReactNode } from 'react' +import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar' + +interface AccessRequestsLayoutProps { + children: ReactNode +} + +export default function AccessRequestsLayout({ children }: AccessRequestsLayoutProps) { + return ( +
+ + {children} +
+ ) +} diff --git a/apps/sim/app/access-requests/loading.tsx b/apps/sim/app/access-requests/loading.tsx new file mode 100644 index 00000000000..4eb54fb68e8 --- /dev/null +++ b/apps/sim/app/access-requests/loading.tsx @@ -0,0 +1,5 @@ +import { AccessRequestsLoading } from '@/components/access-requests/access-requests-loading' + +export default function Loading() { + return +} diff --git a/apps/sim/app/access-requests/page.tsx b/apps/sim/app/access-requests/page.tsx new file mode 100644 index 00000000000..87b5d7b55e8 --- /dev/null +++ b/apps/sim/app/access-requests/page.tsx @@ -0,0 +1,70 @@ +import { Suspense } from 'react' +import { ChipLink } from '@sim/emcn' +import type { Metadata } from 'next' +import { redirect } from 'next/navigation' +import { createSearchParamsCache } from 'nuqs/server' +import { AccessRequestsLoading } from '@/components/access-requests/access-requests-loading' +import { MyAccessRequests } from '@/components/access-requests/my-access-requests' +import { OrganizationAccessRequests } from '@/components/access-requests/organization-access-requests' +import { accessRequestEntrySearchParams } from '@/components/access-requests/search-params' +import { EmptyState } from '@/components/empty-state/empty-state' +import { getSession } from '@/lib/auth' +import { WORKSPACES_PATH } from '@/lib/navigation/paths' +import { buildAuthCrossLink } from '@/app/(auth)/auth-redirect' + +export const metadata: Metadata = { + title: 'Access requests', + robots: { index: false, follow: false }, +} + +interface AccessRequestsPageProps { + searchParams: Promise> +} + +const entrySearchParams = createSearchParamsCache(accessRequestEntrySearchParams) + +/** Session-only entry so access requests remain reachable outside the organization Search rollout. */ +export default async function AccessRequestsPage({ searchParams }: AccessRequestsPageProps) { + const [rawParams, session] = await Promise.all([searchParams, getSession()]) + const params = entrySearchParams.parse(rawParams) + const query = new URLSearchParams() + if (params.organizationId) query.set('organizationId', params.organizationId) + if (params.view !== 'requests') query.set('view', params.view) + if (params.requestId) query.set('requestId', params.requestId) + if (!session?.user) { + redirect( + buildAuthCrossLink('/login', { + callbackUrl: `/access-requests?${query}`, + isInviteFlow: false, + }) + ) + } + + if (!params.organizationId) { + return ( + Your workspaces} + /> + ) + } + + return ( + }> + {params.view === 'admin' ? ( +
+
+
+

Access requests

+ Your workspaces +
+ +
+
+ ) : ( + + )} +
+ ) +} diff --git a/apps/sim/app/api/access-requests/[requestId]/cancel/route.ts b/apps/sim/app/api/access-requests/[requestId]/cancel/route.ts new file mode 100644 index 00000000000..c887c12d619 --- /dev/null +++ b/apps/sim/app/api/access-requests/[requestId]/cancel/route.ts @@ -0,0 +1,23 @@ +import { cancelAccessRequestContract } from '@/lib/api/contracts/access-requests' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { accessRequestOperations } from '@/lib/permission-access-requests/application/operations' +import { cancelAccessRequest } from '@/lib/permission-access-requests/application/requests' + +export const POST = defineInternalJsonRoute({ + contract: cancelAccessRequestContract, + auth: internalSessionAuth, + operation: accessRequestOperations.cancel, + rateLimit: internalRateLimits.user({ + bucketName: 'access-requests:write', + config: { maxTokens: 10, refillRate: 5, refillIntervalMs: 60000 }, + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params, body }) => ({ requestId: params.requestId, scope: body.scope }), + useCase: cancelAccessRequest, + present: ({ request }) => ({ request }), +}) diff --git a/apps/sim/app/api/access-requests/discovery/route.ts b/apps/sim/app/api/access-requests/discovery/route.ts new file mode 100644 index 00000000000..4e0e645fc41 --- /dev/null +++ b/apps/sim/app/api/access-requests/discovery/route.ts @@ -0,0 +1,22 @@ +import { discoverAccessRequestsContract } from '@/lib/api/contracts/access-requests' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { accessRequestOperations } from '@/lib/permission-access-requests/application/operations' +import { discoverAccessRequests } from '@/lib/permission-access-requests/application/requests' + +export const GET = defineInternalJsonRoute({ + contract: discoverAccessRequestsContract, + auth: internalSessionAuth, + operation: accessRequestOperations.discover, + rateLimit: internalRateLimits.user({ + bucketName: 'access-requests:read', + config: { maxTokens: 120, refillRate: 60, refillIntervalMs: 60000 }, + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ query }) => query, + useCase: discoverAccessRequests, +}) diff --git a/apps/sim/app/api/access-requests/route.ts b/apps/sim/app/api/access-requests/route.ts new file mode 100644 index 00000000000..74e2bd961c8 --- /dev/null +++ b/apps/sim/app/api/access-requests/route.ts @@ -0,0 +1,47 @@ +import { + createAccessRequestContract, + listMyAccessRequestsContract, +} from '@/lib/api/contracts/access-requests' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { accessRequestOperations } from '@/lib/permission-access-requests/application/operations' +import { + createAccessRequest, + listMyAccessRequests, +} from '@/lib/permission-access-requests/application/requests' + +export const GET = defineInternalJsonRoute({ + contract: listMyAccessRequestsContract, + auth: internalSessionAuth, + operation: accessRequestOperations.listMine, + rateLimit: internalRateLimits.user({ + bucketName: 'access-requests:read', + config: { maxTokens: 120, refillRate: 60, refillIntervalMs: 60000 }, + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ query }) => ({ + scope: query, + limit: query.limit, + offset: query.offset, + requestId: query.requestId, + }), + useCase: listMyAccessRequests, +}) + +export const POST = defineInternalJsonRoute({ + contract: createAccessRequestContract, + auth: internalSessionAuth, + operation: accessRequestOperations.create, + rateLimit: internalRateLimits.user({ + bucketName: 'access-requests:write', + config: { maxTokens: 10, refillRate: 5, refillIntervalMs: 60000 }, + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ body }) => body, + useCase: createAccessRequest, + present: ({ request }) => ({ request }), +}) diff --git a/apps/sim/app/api/organizations/[id]/access-requests/[requestId]/preview/route.ts b/apps/sim/app/api/organizations/[id]/access-requests/[requestId]/preview/route.ts new file mode 100644 index 00000000000..c4ba646b057 --- /dev/null +++ b/apps/sim/app/api/organizations/[id]/access-requests/[requestId]/preview/route.ts @@ -0,0 +1,22 @@ +import { previewAccessRequestContract } from '@/lib/api/contracts/access-requests' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { accessRequestOperations } from '@/lib/permission-access-requests/application/operations' +import { previewAccessRequest } from '@/lib/permission-access-requests/application/review' + +export const GET = defineInternalJsonRoute({ + contract: previewAccessRequestContract, + auth: internalSessionAuth, + operation: accessRequestOperations.preview, + rateLimit: internalRateLimits.user({ + bucketName: 'access-requests:read', + config: { maxTokens: 120, refillRate: 60, refillIntervalMs: 60000 }, + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params }) => ({ organizationId: params.id, requestId: params.requestId }), + useCase: previewAccessRequest, +}) diff --git a/apps/sim/app/api/organizations/[id]/access-requests/[requestId]/resolve/route.ts b/apps/sim/app/api/organizations/[id]/access-requests/[requestId]/resolve/route.ts new file mode 100644 index 00000000000..0bc389ec7e9 --- /dev/null +++ b/apps/sim/app/api/organizations/[id]/access-requests/[requestId]/resolve/route.ts @@ -0,0 +1,27 @@ +import { resolveAccessRequestContract } from '@/lib/api/contracts/access-requests' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { accessRequestOperations } from '@/lib/permission-access-requests/application/operations' +import { resolveAccessRequest } from '@/lib/permission-access-requests/application/review' + +export const POST = defineInternalJsonRoute({ + contract: resolveAccessRequestContract, + auth: internalSessionAuth, + operation: accessRequestOperations.resolve, + rateLimit: internalRateLimits.user({ + bucketName: 'access-requests:write', + config: { maxTokens: 10, refillRate: 5, refillIntervalMs: 60000 }, + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params, body }) => ({ + organizationId: params.id, + requestId: params.requestId, + decision: body, + }), + useCase: resolveAccessRequest, + present: ({ request }) => ({ request }), +}) diff --git a/apps/sim/app/api/organizations/[id]/access-requests/route.ts b/apps/sim/app/api/organizations/[id]/access-requests/route.ts new file mode 100644 index 00000000000..f68a80694bb --- /dev/null +++ b/apps/sim/app/api/organizations/[id]/access-requests/route.ts @@ -0,0 +1,22 @@ +import { listOrganizationAccessRequestsContract } from '@/lib/api/contracts/access-requests' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { accessRequestOperations } from '@/lib/permission-access-requests/application/operations' +import { listOrganizationAccessRequests } from '@/lib/permission-access-requests/application/requests' + +export const GET = defineInternalJsonRoute({ + contract: listOrganizationAccessRequestsContract, + auth: internalSessionAuth, + operation: accessRequestOperations.listOrganization, + rateLimit: internalRateLimits.user({ + bucketName: 'access-requests:read', + config: { maxTokens: 120, refillRate: 60, refillIntervalMs: 60000 }, + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params, query }) => ({ organizationId: params.id, ...query }), + useCase: listOrganizationAccessRequests, +}) diff --git a/apps/sim/app/api/organizations/[id]/access-requests/settings/route.ts b/apps/sim/app/api/organizations/[id]/access-requests/settings/route.ts new file mode 100644 index 00000000000..6f4ceec0c09 --- /dev/null +++ b/apps/sim/app/api/organizations/[id]/access-requests/settings/route.ts @@ -0,0 +1,41 @@ +import { + getAccessRequestSettingsContract, + updateAccessRequestSettingsContract, +} from '@/lib/api/contracts/access-requests' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { accessRequestOperations } from '@/lib/permission-access-requests/application/operations' +import { + getAccessRequestSettings, + updateAccessRequestSettings, +} from '@/lib/permission-access-requests/application/requests' + +export const GET = defineInternalJsonRoute({ + contract: getAccessRequestSettingsContract, + auth: internalSessionAuth, + operation: accessRequestOperations.getSettings, + rateLimit: internalRateLimits.user({ + bucketName: 'access-requests:read', + config: { maxTokens: 120, refillRate: 60, refillIntervalMs: 60000 }, + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params }) => ({ organizationId: params.id }), + useCase: getAccessRequestSettings, +}) + +export const PATCH = defineInternalJsonRoute({ + contract: updateAccessRequestSettingsContract, + auth: internalSessionAuth, + operation: accessRequestOperations.updateSettings, + rateLimit: internalRateLimits.user({ + bucketName: 'access-requests:write', + config: { maxTokens: 10, refillRate: 5, refillIntervalMs: 60000 }, + }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params, body }) => ({ organizationId: params.id, ...body }), + useCase: updateAccessRequestSettings, +}) diff --git a/apps/sim/app/api/webhooks/outbox/process/route.ts b/apps/sim/app/api/webhooks/outbox/process/route.ts index f79d06a4e28..fe9b2c3a4f0 100644 --- a/apps/sim/app/api/webhooks/outbox/process/route.ts +++ b/apps/sim/app/api/webhooks/outbox/process/route.ts @@ -19,6 +19,7 @@ import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connec import { knowledgeDocumentProcessingOutboxHandlers } from '@/lib/knowledge/documents/processing-outbox-handler' import { recoverKnowledgeDocumentProcessing } from '@/lib/knowledge/documents/processing-recovery' import { organizationResourceCleanupOutboxHandlers } from '@/lib/organizations/resource-cleanup' +import { permissionAccessRequestOutboxHandlers } from '@/lib/permission-access-requests/notifications' import { workspaceFileLiveDocOutboxHandlers } from '@/lib/uploads/contexts/workspace/workspace-file-live-doc-outbox' import { workspaceFileStorageCleanupOutboxHandlers } from '@/lib/uploads/contexts/workspace/workspace-file-storage-cleanup-outbox' import { workflowDeploymentOutboxHandlers } from '@/lib/workflows/deployment-outbox' @@ -44,6 +45,7 @@ const handlers = { ...directGrantOutboxHandlers, ...knowledgeDocumentProcessingOutboxHandlers, ...organizationResourceCleanupOutboxHandlers, + ...permissionAccessRequestOutboxHandlers, ...workspaceFileLiveDocOutboxHandlers, ...workspaceFileStorageCleanupOutboxHandlers, ...workflowDeploymentOutboxHandlers, diff --git a/apps/sim/app/workspace/[workspaceId]/access-requests/loading.tsx b/apps/sim/app/workspace/[workspaceId]/access-requests/loading.tsx new file mode 100644 index 00000000000..4eb54fb68e8 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/access-requests/loading.tsx @@ -0,0 +1,5 @@ +import { AccessRequestsLoading } from '@/components/access-requests/access-requests-loading' + +export default function Loading() { + return +} diff --git a/apps/sim/app/workspace/[workspaceId]/access-requests/page.tsx b/apps/sim/app/workspace/[workspaceId]/access-requests/page.tsx new file mode 100644 index 00000000000..b3d49e88885 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/access-requests/page.tsx @@ -0,0 +1,19 @@ +import { Suspense } from 'react' +import type { Metadata } from 'next' +import { AccessRequestsLoading } from '@/components/access-requests/access-requests-loading' +import { MyAccessRequests } from '@/components/access-requests/my-access-requests' + +export const metadata: Metadata = { title: 'My access requests' } + +interface AccessRequestsPageProps { + params: Promise<{ workspaceId: string }> +} + +export default async function AccessRequestsPage({ params }: AccessRequestsPageProps) { + const { workspaceId } = await params + return ( + }> + + + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/files-empty-state.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/files-empty-state.tsx index 4fb2d63be38..a3d2e8df7e1 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/files-empty-state.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/files-empty-state.tsx @@ -1,6 +1,6 @@ import { Chip, cn } from '@sim/emcn' import { Upload } from '@sim/emcn/icons' -import { EmptyState } from '@/components/empty-state/empty-state' +import { EmptyState, type EmptyStateProps } from '@/components/empty-state/empty-state' import { EmptyStateDocsLink } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/docs-link' import { HAIRLINE } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/hairline' import { MASK_NO_REPEAT } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/mask' @@ -64,25 +64,39 @@ function FilesGraphic() { ) } -interface FilesEmptyStateProps { +interface UploadFilesEmptyStateProps { /** Opens the file picker — the same action the header's upload chip runs. */ onUpload: () => void /** Mirrors the header chip's disabled state: no edit rights, or an upload in flight. */ uploadDisabled?: boolean } -/** Empty state for the files list when the workspace has none. */ -export function FilesEmptyState({ onUpload, uploadDisabled = false }: FilesEmptyStateProps) { +type FilesEmptyStateProps = UploadFilesEmptyStateProps | Omit + +/** Shared file illustration and actions for empty or unavailable files. */ +export function FilesEmptyState(props: FilesEmptyStateProps) { + const content = 'title' in props ? props : undefined return ( } - title='Files' - description='Upload files to share them across your team and every agent.' + title={content?.title ?? 'Files'} + description={ + content?.description ?? 'Upload files to share them across your team and every agent.' + } action={ <> - - Upload - + {'onUpload' in props ? ( + + Upload + + ) : ( + content?.action + )} } diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-empty-state.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-empty-state.tsx index dcfdc7a3664..0ac6e3a0d42 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-empty-state.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-empty-state.tsx @@ -1,33 +1,44 @@ import { Chip } from '@sim/emcn' import { Plus } from '@sim/emcn/icons' -import { EmptyState } from '@/components/empty-state/empty-state' +import { EmptyState, type EmptyStateProps } from '@/components/empty-state/empty-state' import { EmptyStateDocsLink } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/docs-link' import { KnowledgeIsoMark } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/knowledge-iso' const KNOWLEDGE_DOCS_URL = 'https://docs.sim.ai/knowledgebase' -interface KnowledgeEmptyStateProps { +interface CreateKnowledgeEmptyStateProps { /** Opens the create-base modal — the same action the header's primary chip runs. */ onCreate: () => void /** Mirrors the header chip's disabled state: no edit rights on the workspace. */ createDisabled?: boolean } -/** Empty state for the knowledge bases list when the workspace has none. */ -export function KnowledgeEmptyState({ - onCreate, - createDisabled = false, -}: KnowledgeEmptyStateProps) { +type KnowledgeEmptyStateProps = CreateKnowledgeEmptyStateProps | Omit + +/** Shared knowledge illustration and actions for empty or unavailable bases. */ +export function KnowledgeEmptyState(props: KnowledgeEmptyStateProps) { + const content = 'title' in props ? props : undefined return ( } - title='Knowledge bases' - description='Upload documents to give your agents a memory they can search.' + title={content?.title ?? 'Knowledge bases'} + description={ + content?.description ?? 'Upload documents to give your agents a memory they can search.' + } action={ <> - - New base - + {'onCreate' in props ? ( + + New base + + ) : ( + content?.action + )} } diff --git a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/tables-empty-state.tsx b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/tables-empty-state.tsx index 1026d13ed3d..1f3cb12b3b4 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/tables-empty-state.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/tables-empty-state.tsx @@ -1,6 +1,6 @@ import { Chip, cn } from '@sim/emcn' import { Plus } from '@sim/emcn/icons' -import { EmptyState } from '@/components/empty-state/empty-state' +import { EmptyState, type EmptyStateProps } from '@/components/empty-state/empty-state' import { EmptyStateDocsLink } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/docs-link' import { MASK_NO_REPEAT } from '@/app/workspace/[workspaceId]/components/resource/components/resource-empty-state/mask' @@ -105,25 +105,40 @@ function TablesGraphic() { const TABLES_DOCS_URL = 'https://docs.sim.ai/tables' -interface TablesEmptyStateProps { +interface CreateTableEmptyStateProps { /** Creates a table — the same action the header's primary chip runs. */ onCreate: () => void /** Mirrors the header chip's disabled state: no edit rights, or a create already in flight. */ createDisabled?: boolean } -/** Empty state for the tables list when the workspace has none. */ -export function TablesEmptyState({ onCreate, createDisabled = false }: TablesEmptyStateProps) { +type TablesEmptyStateProps = CreateTableEmptyStateProps | Omit + +/** Shared table illustration and actions for empty or unavailable tables. */ +export function TablesEmptyState(props: TablesEmptyStateProps) { + const content = 'title' in props ? props : undefined return ( } - title='Tables' - description='Create a table to store structured data your agents can read and write.' + title={content?.title ?? 'Tables'} + description={ + content?.description ?? + 'Create a table to store structured data your agents can read and write.' + } action={ <> - - New table - + {'onCreate' in props ? ( + + New table + + ) : ( + content?.action + )} } diff --git a/apps/sim/app/workspace/[workspaceId]/files/files.tsx b/apps/sim/app/workspace/[workspaceId]/files/files.tsx index f18841c0821..610456cd742 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/files.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/files.tsx @@ -24,6 +24,7 @@ import { getErrorMessage, toError } from '@sim/utils/errors' import { useParams, useRouter } from 'next/navigation' import { useQueryStates } from 'nuqs' import { usePostHog } from 'posthog-js/react' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' import { getDocumentIcon } from '@/components/icons/document-icons' import { useLimitUpgradeToast } from '@/lib/billing/client' import { captureEvent } from '@/lib/posthog/client' @@ -264,6 +265,14 @@ function formatFileType(storedType: string | null, filename: string): string { } export function Files() { + return ( + + + + ) +} + +function FilesContent() { const fileInputRef = useRef(null) const saveRef = useRef<(() => Promise) | null>(null) const downloadSourceRef = useRef(null) diff --git a/apps/sim/app/workspace/[workspaceId]/files/prefetch.ts b/apps/sim/app/workspace/[workspaceId]/files/prefetch.ts index dd08f5fc925..9a6772782d4 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/prefetch.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/prefetch.ts @@ -1,7 +1,9 @@ import type { QueryClient } from '@tanstack/react-query' import { listWorkspaceFileFoldersContract } from '@/lib/api/contracts/workspace-file-folders' import { listWorkspaceFileFolders } from '@/lib/uploads/contexts/workspace/workspace-file-folder-manager' +import { listAllWorkspaceFiles } from '@/lib/workspace-files/application/list-workspace-files' import { getWorkspaceHostContextForViewer } from '@/lib/workspaces/host-context' +import { authorizeResourcePrefetch } from '@/app/workspace/[workspaceId]/lib/authorize-resource-prefetch' import { prefetchResourceListChrome } from '@/app/workspace/[workspaceId]/lib/prefetch-resource-list-chrome' import { seedWorkspaceFiles } from '@/app/workspace/[workspaceId]/lib/seed-workspace-files' import { @@ -35,6 +37,7 @@ export async function prefetchFilesBrowser( if (!userId) return const hostContext = await getWorkspaceHostContextForViewer(workspaceId, userId) if (!hostContext) return + if (!(await authorizeResourcePrefetch(listAllWorkspaceFiles, workspaceId))) return await Promise.all([ queryClient.prefetchQuery({ diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx index 73ab07b3539..f68a9e8bcfb 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx @@ -5,6 +5,7 @@ import { cn, Expandable, ExpandableContent, SecretReveal, Tooltip, toast } from import { ArrowRight, Check, ChevronDown, SquareArrowUpRight, TerminalWindow } from '@sim/emcn/icons' import { isRecordLike } from '@sim/utils/object' import { useParams } from 'next/navigation' +import { MemberLimitRequestAction } from '@/components/access-requests/member-limit-request-action' import { useSession } from '@/lib/auth/auth-client' import { buildHostedUpgradeUrl, HOSTED_BILLING_SETTINGS_URL } from '@/lib/billing/upgrade-reasons' import { canManageWorkspaceBilling } from '@/lib/billing/workspace-permissions' @@ -76,6 +77,7 @@ import { useTablesList } from '@/hooks/queries/tables' import { findWorkspaceFileByPath } from '@/hooks/queries/utils/find-workspace-file-by-src' import { useWorkflows } from '@/hooks/queries/workflows' import { useWorkspaceFiles } from '@/hooks/queries/workspace-files' +import { useWorkspaceUsageGate } from '@/hooks/queries/workspace-usage' import { useSettingsNavigation } from '@/hooks/use-settings-navigation' export interface OptionsItemData { @@ -3184,6 +3186,9 @@ function UsageUpgradeDisplay({ data }: { data: UsageUpgradeTagData }) { ? buildHostedUpgradeUrl() : HOSTED_BILLING_SETTINGS_URL const canManageBilling = !hosted || canManageWorkspaceBilling(hostContext, session?.user?.id) + const usageGate = useWorkspaceUsageGate( + data.action === 'increase_limit' && !canManageBilling ? hostContext.workspace.id : undefined + ) const unavailableMessage = hostContext.hostOrganizationId ? 'Contact an organization admin to manage this workspace’s usage limits.' : 'Only the workspace owner can manage this workspace’s usage limits.' @@ -3225,7 +3230,16 @@ function UsageUpgradeDisplay({ data }: { data: UsageUpgradeTagData }) { {hosted ? : } ) : ( -

{unavailableMessage}

+
+

{unavailableMessage}

+ {usageGate.isSuccess && + usageGate.data.isExceeded && + usageGate.data.scope === 'member' && ( + + )} +
)}
) diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/usage-upgrade-display.test.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/usage-upgrade-display.test.tsx new file mode 100644 index 00000000000..77a2ea45758 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/usage-upgrade-display.test.tsx @@ -0,0 +1,67 @@ +/** + * @vitest-environment jsdom + */ +import { act } from 'react' +import { createRoot } from 'react-dom/client' +import { describe, expect, it, vi } from 'vitest' + +const { usageGate } = vi.hoisted(() => ({ usageGate: vi.fn() })) +vi.mock('@/hooks/queries/workspace-usage', () => ({ useWorkspaceUsageGate: usageGate })) +vi.mock('@/lib/auth/auth-client', () => ({ + useSession: () => ({ data: { user: { id: 'member' } } }), +})) +vi.mock('@/lib/core/config/deployment-shape', async (importOriginal) => ({ + ...(await importOriginal()), + useDeploymentShape: () => ({ hosted: true }), +})) +vi.mock('@/app/workspace/[workspaceId]/providers/workspace-host-provider', () => ({ + useWorkspaceHostContext: () => ({ + workspace: { id: 'workspace', billedAccountUserId: 'owner' }, + hostOrganizationId: 'organization', + viewer: { isHostOrganizationAdmin: false }, + }), +})) +vi.mock('@/hooks/use-settings-navigation', () => ({ + useSettingsNavigation: () => ({ getSettingsHref: () => '/settings/billing' }), +})) +vi.mock('@/components/access-requests/member-limit-request-action', () => ({ + MemberLimitRequestAction: () => , +})) + +import { SpecialTags } from '@/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags' + +describe('usage-limit request action', () => { + it.each([ + { scope: 'payer', isExceeded: true, isSuccess: true, visible: false }, + { scope: 'member', isExceeded: true, isSuccess: true, visible: true }, + { scope: 'member', isExceeded: false, isSuccess: true, visible: false }, + { scope: 'member', isExceeded: true, isSuccess: false, visible: false }, + ])( + 'offers the remedy for the current cap ($scope, exceeded $isExceeded, loaded $isSuccess)', + ({ scope, isExceeded, isSuccess, visible }) => { + usageGate.mockReturnValue({ isSuccess, data: { scope, isExceeded } }) + ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + const container = document.createElement('div') + const root = createRoot(container) + try { + act(() => + root.render( + + ) + ) + expect(container.textContent?.includes('Request increase')).toBe(visible) + } finally { + act(() => root.unmount()) + } + } + ) +}) diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx index 05e78e0d2f1..ff7515d1711 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx @@ -19,6 +19,7 @@ import { useQueryClient } from '@tanstack/react-query' import { useParams, useRouter } from 'next/navigation' import { useQueryState } from 'nuqs' import { usePostHog } from 'posthog-js/react' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' import { requestJson } from '@/lib/api/client/request' import { createWorkflowContract } from '@/lib/api/contracts' import { @@ -91,7 +92,15 @@ interface HomeProps { userId?: string } -export function Home({ chatId, userName, userId }: HomeProps) { +export function Home(props: HomeProps) { + return ( + + + + ) +} + +function HomeContent({ chatId, userName, userId }: HomeProps) { useOAuthReturnRouter() const { workspaceId } = useParams<{ workspaceId: string }>() const router = useRouter() diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/[block]/page.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/[block]/page.tsx index d7765df9eb5..51890cea304 100644 --- a/apps/sim/app/workspace/[workspaceId]/integrations/[block]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/integrations/[block]/page.tsx @@ -1,6 +1,7 @@ import { Suspense } from 'react' import type { Metadata } from 'next' import { notFound } from 'next/navigation' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' import { INTEGRATIONS } from '@/lib/integrations' import { IntegrationBlockDetail } from '@/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail' import { IntegrationBlockDetailFallback } from '@/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail-fallback' @@ -27,8 +28,10 @@ export default async function IntegrationBlockPage({ if (!integration) notFound() return ( - }> - - + + }> + + + ) } diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/page.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/page.tsx index 88003c84119..4267bc40e60 100644 --- a/apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/page.tsx @@ -1,4 +1,5 @@ import type { Metadata } from 'next' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' import { ConnectedCredentialDetail } from '@/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail' export const metadata: Metadata = { @@ -11,5 +12,9 @@ export default async function ConnectedCredentialPage({ params: Promise<{ workspaceId: string; credentialId: string }> }) { const { workspaceId, credentialId } = await params - return + return ( + + + + ) } diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx index 3fc4ab345da..a1b1ea26755 100644 --- a/apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx +++ b/apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx @@ -13,6 +13,7 @@ import { } from '@sim/emcn' import { useParams } from 'next/navigation' import { useQueryStates } from 'nuqs' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' import { blockTypeToIconMap, formatIntegrationType, @@ -138,6 +139,14 @@ function ConnectedItem({ href, blockType, name, description, icon: Icon }: Conne } export function Integrations() { + return ( + + + + ) +} + +function IntegrationsContent() { const scrollContainerRef = useRef(null) const params = useParams() const workspaceId = (params?.workspaceId as string) || '' diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/page.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/page.tsx index 0753a1df61d..a49bbd1ee6d 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/page.tsx @@ -1,5 +1,6 @@ import { Suspense } from 'react' import type { Metadata } from 'next' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' import { Document } from '@/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document' import DocumentLoading from '@/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/loading' @@ -26,12 +27,14 @@ export default async function DocumentChunksPage({ params, searchParams }: Docum return ( }> - + + + ) } diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/page.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/page.tsx index 36e58dffe07..a441f86dfd8 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/page.tsx @@ -1,5 +1,6 @@ import { Suspense } from 'react' import type { Metadata } from 'next' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' import { KnowledgeBase } from '@/app/workspace/[workspaceId]/knowledge/[id]/base' import KnowledgeBaseLoading from '@/app/workspace/[workspaceId]/knowledge/[id]/loading' @@ -22,7 +23,9 @@ export default async function KnowledgeBasePage({ params, searchParams }: PagePr return ( }> - + + + ) } diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.test.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.test.tsx index 6496a088c2e..a03d3348d72 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.test.tsx @@ -34,6 +34,12 @@ vi.mock('nuqs', () => ({ useQueryStates: () => [{ search: '', connector: [], content: [], owner: [] }, vi.fn()], })) vi.mock('@/hooks/use-permission-config', () => ({ usePermissionConfig: () => ({ config: {} }) })) +vi.mock('@/ee/access-control/hooks/permission-groups', () => ({ + useUserPermissionConfig: () => ({ data: { config: {} }, isPending: false }), +})) +vi.mock('@/hooks/queries/access-requests', () => ({ + useDiscoverAccessRequests: () => ({ data: { enabled: false, entries: [] }, isPending: false }), +})) vi.mock('@/app/workspace/[workspaceId]/providers/workspace-permissions-provider', () => ({ useUserPermissionsContext: () => mocks.permissions, })) diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx index 530208ed9ce..2bcfc892f11 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx @@ -8,6 +8,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { useParams, useRouter } from 'next/navigation' import { useQueryStates } from 'nuqs' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' import { MAX_KNOWLEDGE_BATCH_ITEMS } from '@/lib/knowledge/constants' import type { KnowledgeBaseData } from '@/lib/knowledge/types' import { SEARCH_DEBOUNCE_MS } from '@/lib/url-state' @@ -194,6 +195,14 @@ function connectorCell(connectorTypes?: string[]): ResourceCell { } export function Knowledge() { + return ( + + + + ) +} + +function KnowledgeContent() { const params = useParams() const router = useRouter() const workspaceId = params.workspaceId as string diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/prefetch.ts b/apps/sim/app/workspace/[workspaceId]/knowledge/prefetch.ts index 7aad80a5bca..f5080114a9c 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/prefetch.ts +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/prefetch.ts @@ -2,7 +2,11 @@ import type { QueryClient } from '@tanstack/react-query' import { listKnowledgeBasesContract } from '@/lib/api/contracts/knowledge' import { internalSessionAuth } from '@/lib/api/server/routes' import { internalKnowledgePresenters } from '@/lib/knowledge/api/internal-route' -import { listInternalKnowledgeBases } from '@/lib/knowledge/application/knowledge-bases' +import { + listInternalKnowledgeBases, + listKnowledgeBases, +} from '@/lib/knowledge/application/knowledge-bases' +import { authorizeResourcePrefetch } from '@/app/workspace/[workspaceId]/lib/authorize-resource-prefetch' import { prefetchResourceFolders } from '@/app/workspace/[workspaceId]/lib/prefetch-resource-folders' import { prefetchResourceListChrome } from '@/app/workspace/[workspaceId]/lib/prefetch-resource-list-chrome' import { KNOWLEDGE_BASE_LIST_STALE_TIME, knowledgeKeys } from '@/hooks/queries/utils/knowledge-keys' @@ -37,6 +41,7 @@ export async function prefetchKnowledgeBases( userId: string | undefined ): Promise { if (!userId) return + if (!(await authorizeResourcePrefetch(listKnowledgeBases, workspaceId))) return await Promise.all([ queryClient.prefetchQuery({ diff --git a/apps/sim/app/workspace/[workspaceId]/lib/authorize-resource-prefetch.ts b/apps/sim/app/workspace/[workspaceId]/lib/authorize-resource-prefetch.ts new file mode 100644 index 00000000000..ae63bece1a9 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/lib/authorize-resource-prefetch.ts @@ -0,0 +1,28 @@ +import { internalSessionAuth } from '@/lib/api/server/routes' +import { InternalUnauthenticatedError } from '@/lib/api/server/routes/internal-json-route' +import type { AuthorizingUseCase } from '@/lib/core/application/authorized-workspace-use-case' +import type { ApplicationOperation } from '@/lib/core/application/operation' +import { OrchestrationError } from '@/lib/core/orchestration/types' + +/** Prove module access through its application operation before seeding resource data or chrome. */ +export async function authorizeResourcePrefetch( + useCase: Pick< + AuthorizingUseCase, + 'authorize' + >, + workspaceId: string +): Promise { + try { + const principal = await internalSessionAuth.authenticate() + await useCase.authorize({ principal, input: { workspaceId } }) + return true + } catch (error) { + if (error instanceof InternalUnauthenticatedError) return false + if ( + error instanceof OrchestrationError && + (error.code === 'forbidden' || error.code === 'not_found' || error.code === 'unauthorized') + ) + return false + throw error + } +} diff --git a/apps/sim/app/workspace/[workspaceId]/lib/prefetch.test.ts b/apps/sim/app/workspace/[workspaceId]/lib/prefetch.test.ts index 6bb77574bb4..fada5c1bb1d 100644 --- a/apps/sim/app/workspace/[workspaceId]/lib/prefetch.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/lib/prefetch.test.ts @@ -1,10 +1,14 @@ /** * @vitest-environment node */ + import { QueryClient } from '@tanstack/react-query' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { InternalUnauthenticatedError } from '@/lib/api/server/routes/internal-json-route' +import { OrchestrationError } from '@/lib/core/orchestration/types' const { + mockAuthorizeResource, mockAuthenticate, mockGetWorkspaceHostContextForViewer, mockGetWorkspaceMemberProfiles, @@ -21,6 +25,7 @@ const { mockListWorkspaceFileFolders, mockListWorkspaceFilesWithShares, } = vi.hoisted(() => ({ + mockAuthorizeResource: vi.fn(), mockAuthenticate: vi.fn(), mockGetWorkspaceHostContextForViewer: vi.fn(), mockGetWorkspaceMemberProfiles: vi.fn(), @@ -69,6 +74,12 @@ vi.mock('@/lib/users/queries', () => ({ vi.mock('@/lib/copilot/chat/list-mothership-chats', () => ({ listMothershipChats: mockListMothershipChats, })) +vi.mock('@/lib/table/application/tables', () => ({ + listTableDefinitionsUseCase: { authorize: mockAuthorizeResource }, +})) +vi.mock('@/lib/workspace-files/application/list-workspace-files', () => ({ + listAllWorkspaceFiles: { authorize: mockAuthorizeResource }, +})) vi.mock('@/lib/table/service', () => ({ listTables: mockListTables, })) @@ -85,7 +96,10 @@ vi.mock('@/lib/api/server/routes', () => ({ internalSessionAuth: { authenticate: mockAuthenticate }, })) vi.mock('@/lib/knowledge/application/knowledge-bases', () => ({ - listInternalKnowledgeBases: { execute: mockListInternalKnowledgeBases }, + listKnowledgeBases: { authorize: mockAuthorizeResource }, + listInternalKnowledgeBases: { + execute: mockListInternalKnowledgeBases, + }, })) vi.mock('@/lib/knowledge/api/internal-route', () => ({ internalKnowledgePresenters: { list: mockKnowledgePresenterList }, @@ -117,6 +131,7 @@ function makeClient() { describe('workspace list prefetches', () => { beforeEach(() => { vi.clearAllMocks() + mockAuthorizeResource.mockResolvedValue(undefined) mockGetWorkspaceHostContextForViewer.mockResolvedValue({ viewer: { permission: 'admin' } }) mockListFoldersForWorkspace.mockResolvedValue([]) mockListWorkspaceFilesWithShares.mockResolvedValue([]) @@ -195,6 +210,24 @@ describe('workspace list prefetches', () => { }) }) + it.each([prefetchTables, prefetchKnowledgeBases, prefetchFilesBrowser])( + 'seeds no protected data or chrome when the module operation refuses access', + async (prefetch) => { + mockAuthorizeResource.mockRejectedValue( + new OrchestrationError('forbidden', 'Module withheld') + ) + const client = makeClient() + await prefetch(client, WORKSPACE_ID, USER_ID) + expect(client.getQueryCache().getAll()).toHaveLength(0) + expect(mockListTables).not.toHaveBeenCalled() + expect(mockListInternalKnowledgeBases).not.toHaveBeenCalled() + expect(mockListWorkspaceFilesWithShares).not.toHaveBeenCalled() + expect(mockListFoldersForWorkspace).not.toHaveBeenCalled() + expect(mockListWorkspaceFileFolders).not.toHaveBeenCalled() + expect(mockListPinnedItemsForUser).not.toHaveBeenCalled() + } + ) + describe('prefetchKnowledgeBases', () => { /** * The bases list is a protected read behind an application operation, so the prefetch runs @@ -215,7 +248,7 @@ describe('workspace list prefetches', () => { }) it('caches nothing when the session principal cannot be built', async () => { - mockAuthenticate.mockRejectedValue(new Error('Unauthorized')) + mockAuthenticate.mockRejectedValue(new InternalUnauthenticatedError()) const client = makeClient() await prefetchKnowledgeBases(client, WORKSPACE_ID, USER_ID) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.test.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.test.tsx index bfd7f13b9f7..04e42904940 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.test.tsx @@ -28,6 +28,9 @@ const { vi.mock('next/navigation', () => ({ notFound: mockNotFound, redirect: mockRedirect })) vi.mock('@/lib/auth', () => ({ getSession: mockGetSession })) +vi.mock('@/components/access-requests/permission-access-boundary', () => ({ + PermissionAccessBoundary: vi.fn(() => null), +})) vi.mock('@/lib/settings/application/workspace-section-access', () => ({ authorizeWorkspaceSettingsSection: mockAuthorizeSection, })) @@ -182,6 +185,22 @@ describe('WorkspaceSettingsSectionPage', () => { expect(mockGetQueryClient).not.toHaveBeenCalled() }) + it('renders a request-only boundary without protected children or section prefetches', async () => { + mockAuthorizeSection.mockResolvedValue({ + allowed: false, + disposition: 'request-access', + configKey: 'hideApiKeysTab', + }) + + const element = await WorkspaceSettingsSectionPage(pageProps('billing')) + + expect(element.props.children.props).toEqual({ configKey: 'hideApiKeysTab' }) + expect(mockSectionPrefetch).not.toHaveBeenCalled() + expect(mockGetQueryClient).not.toHaveBeenCalled() + expect(mockGetHostContext).not.toHaveBeenCalled() + expect(mockRedirect).not.toHaveBeenCalled() + }) + it('redirects unavailable visible-catalog sections to General', async () => { mockAuthorizeSection.mockResolvedValue({ allowed: false, disposition: 'redirect-general' }) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx index 04ba51f88e6..97cf21c2783 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx @@ -2,6 +2,8 @@ import { Suspense } from 'react' import { dehydrate, HydrationBoundary } from '@tanstack/react-query' import type { Metadata } from 'next' import { notFound, redirect } from 'next/navigation' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' +import { EmptyState } from '@/components/empty-state/empty-state' import { getOrganizationSettingsHref, UNIFIED_TO_ORGANIZATION_SECTION, @@ -54,6 +56,20 @@ export default async function WorkspaceSettingsSectionPage({ }) if (!access.allowed) { if (access.disposition === 'not-found') notFound() + if (access.disposition === 'request-access') { + return ( + + } + > + + + ) + } redirectToGeneralSettings(workspaceId) } diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx index 6fc7c8495aa..016de4cb3e4 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx @@ -3,6 +3,8 @@ import { useEffect } from 'react' import dynamic from 'next/dynamic' import { usePostHog } from 'posthog-js/react' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' +import { getSettingsPermissionConfigKey } from '@/components/settings/navigation' import { useSession } from '@/lib/auth/auth-client' import { useDeploymentShape } from '@/lib/core/config/deployment-shape' import { captureEvent } from '@/lib/posthog/client' @@ -128,7 +130,17 @@ interface SettingsPageProps { section: SettingsSection } -export function SettingsPage({ section }: SettingsPageProps) { +export function SettingsPage(props: SettingsPageProps) { + const configKey = getSettingsPermissionConfigKey(props.section) + if (!configKey) return + return ( + + + + ) +} + +function SettingsPageContent({ section }: SettingsPageProps) { const { data: session, isPending: sessionLoading } = useSession() const hostContext = useWorkspaceHostContext() const { billingEnabled } = useDeploymentShape() diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/page.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/page.tsx index 9f3c382c3ff..152bd121970 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/page.tsx @@ -1,5 +1,6 @@ import { Suspense } from 'react' import type { Metadata } from 'next' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' import TableLoading from '@/app/workspace/[workspaceId]/tables/[tableId]/loading' import { Table } from './table' @@ -15,7 +16,9 @@ export const metadata: Metadata = { export default function TablePage() { return ( }> -
+ +
+ ) } diff --git a/apps/sim/app/workspace/[workspaceId]/tables/prefetch.ts b/apps/sim/app/workspace/[workspaceId]/tables/prefetch.ts index a937a26e753..c9cbbe31fb3 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/prefetch.ts +++ b/apps/sim/app/workspace/[workspaceId]/tables/prefetch.ts @@ -1,7 +1,9 @@ import type { QueryClient } from '@tanstack/react-query' +import { listTableDefinitionsUseCase } from '@/lib/table/application/tables' import { listTables } from '@/lib/table/service' import { toTableListItem } from '@/lib/table/wire' import { getWorkspaceHostContextForViewer } from '@/lib/workspaces/host-context' +import { authorizeResourcePrefetch } from '@/app/workspace/[workspaceId]/lib/authorize-resource-prefetch' import { prefetchResourceFolders } from '@/app/workspace/[workspaceId]/lib/prefetch-resource-folders' import { prefetchResourceListChrome } from '@/app/workspace/[workspaceId]/lib/prefetch-resource-list-chrome' import { TABLE_LIST_STALE_TIME, tableKeys } from '@/hooks/queries/utils/table-keys' @@ -33,6 +35,7 @@ export async function prefetchTables( if (!userId) return const hostContext = await getWorkspaceHostContextForViewer(workspaceId, userId) if (!hostContext) return + if (!(await authorizeResourcePrefetch(listTableDefinitionsUseCase, workspaceId))) return await Promise.all([ queryClient.prefetchQuery({ diff --git a/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx b/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx index b7876cd65f2..4c68bbdb44c 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/tables.tsx @@ -8,6 +8,7 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { useParams, useRouter } from 'next/navigation' import { useQueryStates } from 'nuqs' +import { PermissionAccessBoundary } from '@/components/access-requests/permission-access-boundary' import type { TableDefinition } from '@/lib/table' import { generateUniqueTableName, MAX_TABLE_BATCH_ITEMS } from '@/lib/table/constants' import { SEARCH_DEBOUNCE_MS } from '@/lib/url-state' @@ -133,6 +134,14 @@ type TableResourceItem = | { kind: 'folder'; folder: WorkflowFolder } export function Tables() { + return ( + + + + ) +} + +function TablesContent() { const params = useParams() const router = useRouter() const workspaceId = params.workspaceId as string diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.test.tsx new file mode 100644 index 00000000000..506d756270e --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.test.tsx @@ -0,0 +1,207 @@ +/** + * @vitest-environment jsdom + */ +import { act, type ReactNode } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const { addBlock, dragBlock, discovery, toolbarState } = vi.hoisted(() => ({ + addBlock: vi.fn(), + dragBlock: vi.fn(), + discovery: vi.fn(), + toolbarState: { + expandedSections: { triggers: true, blocks: true, customBlocks: true, tools: true }, + setSectionExpanded: vi.fn(), + }, +})) + +vi.mock('next/navigation', () => ({ useParams: () => ({ workspaceId: 'workspace-1' }) })) +vi.mock('posthog-js/react', () => ({ usePostHog: () => null })) +vi.mock('@/lib/posthog/client', () => ({ captureEvent: vi.fn() })) +vi.mock('@sim/emcn', () => ({ + Button: ({ children, onClick }: { children: ReactNode; onClick: () => void }) => ( + + ), + chipVariants: () => '', + cn: (...values: unknown[]) => values.filter(Boolean).join(' '), + Expandable: ({ children, expanded }: { children: ReactNode; expanded: boolean }) => + expanded ? children : null, + ExpandableContent: ({ children }: { children: ReactNode }) => children, + Info: () => null, + OverflowText: ({ label }: { label: string }) => {label}, + handleKeyboardActivation: (event: React.KeyboardEvent, callback: () => void) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault() + event.stopPropagation() + callback() + } + }, +})) +vi.mock('@sim/emcn/icons', () => ({ + ChevronDown: () => null, + Lock: () => null, + Search: () => null, +})) +vi.mock('@/blocks/block-tile', () => ({ BlockTile: () => null })) +vi.mock('@/blocks/custom/build-config', () => ({ + isCustomBlockType: () => false, + buildCustomBlockConfig: vi.fn(), +})) +vi.mock('@/blocks/custom/client-overlay', () => ({ useCustomBlockOverlayVersion: () => 1 })) +vi.mock('@/blocks/custom/custom-block-icon', () => ({ getCustomBlockTile: vi.fn() })) +vi.mock('@/blocks/registry', () => ({ + getCanonicalBlocksByCategory: (category: string) => + category === 'blocks' + ? [ + { name: 'Allowed core', type: 'allowed-core' }, + { name: 'Locked core', type: 'locked-core' }, + ] + : [ + { name: 'Allowed tool', type: 'allowed-tool' }, + { name: 'Locked tool', type: 'locked-tool' }, + ], +})) +vi.mock('@/lib/workflows/triggers/trigger-utils', () => ({ + getTriggersForSidebar: () => [ + { name: 'Allowed trigger', type: 'allowed-trigger' }, + { name: 'Locked trigger', type: 'locked-trigger' }, + ], + hasTriggerCapability: () => true, +})) +vi.mock('@/ee/whitelabeling/components/branding-provider', () => ({ + useOrgBrandConfig: () => ({}), +})) +vi.mock('@/hooks/queries/custom-blocks', () => ({ useCustomBlocks: () => ({ data: [] }) })) +vi.mock('@/hooks/use-sandbox-block-constraints', () => ({ useSandboxBlockConstraints: () => null })) +vi.mock('@/hooks/use-permission-config', () => ({ + usePermissionConfig: () => ({ + filterBlocks: (items: T[]) => + items.filter((item) => !item.type.startsWith('locked-')), + isBlockRequestable: (type: string) => type.startsWith('locked-'), + }), +})) +vi.mock('@/components/access-requests/permission-access-boundary', () => ({ + useWorkspaceAccessRequestFeatures: discovery, +})) +vi.mock('@/components/access-requests/request-access-action', () => ({ + RequestAccessModal: ({ label, onClose }: { label: string; onClose: () => void }) => ( +
+ Request {label} + +
+ ), +})) +vi.mock('@/stores/panel', () => ({ + useToolbarStore: (selector: (state: typeof toolbarState) => unknown) => selector(toolbarState), +})) +vi.mock( + '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/hooks', + () => ({ + useToolbarItemInteractions: () => ({ handleItemClick: addBlock, handleDragStart: dragBlock }), + }) +) +vi.mock( + '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/components', + () => ({ ToolbarItemContextMenu: () => null }) +) +vi.mock( + '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/loop/loop-config', + () => ({ LoopTool: { name: 'Loop', type: 'loop' } }) +) +vi.mock( + '@/app/workspace/[workspaceId]/w/[workflowId]/components/subflows/parallel/parallel-config', + () => ({ ParallelTool: { name: 'Parallel', type: 'parallel' } }) +) + +import { Toolbar } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar' + +describe('toolbar access requests', () => { + let container: HTMLDivElement + let root: Root + + beforeEach(() => { + vi.clearAllMocks() + ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + discovery.mockReturnValue({ data: { enabled: true } }) + }) + + afterEach(() => { + act(() => root.unmount()) + container.remove() + }) + + it('places every enabled category above one restricted section in trigger/core/integration order', () => { + act(() => root.render()) + const sections = Array.from(container.querySelectorAll('section')) + expect(sections).toHaveLength(4) + expect(sections.at(-1)?.getAttribute('aria-label')).toBe('Access required') + expect( + Array.from(sections.at(-1)!.querySelectorAll('[role="button"]')).map((row) => row.textContent) + ).toEqual(['Locked trigger', 'Locked core', 'Locked tool']) + expect(sections.slice(0, -1).every((section) => !section.textContent?.includes('Locked'))).toBe( + true + ) + }) + + it.each(['click', 'Enter', ' '])( + 'opens a request with %s without inserting or dragging a block', + (activation) => { + act(() => root.render()) + const row = container.querySelector( + '[aria-label="Request access to Locked tool"]' + )! + expect(row.draggable).toBe(false) + act(() => { + row.dispatchEvent(new Event('dragstart', { bubbles: true })) + row.dispatchEvent( + activation === 'click' + ? new MouseEvent('click', { bubbles: true }) + : new KeyboardEvent('keydown', { key: activation, bubbles: true }) + ) + }) + expect(container.querySelector('[role="dialog"]')?.textContent).toContain( + 'Request Locked tool' + ) + expect(addBlock).not.toHaveBeenCalled() + expect(dragBlock).not.toHaveBeenCalled() + } + ) + + it('moves keyboard focus from enabled rows through the restricted section', async () => { + act(() => root.render()) + act(() => + container.querySelector('[data-toolbar-root] > [role="button"]')!.click() + ) + const input = container.querySelector('input')! + act(() => input.focus()) + const rows = Array.from( + container.querySelectorAll( + '[aria-label^="Add "], [aria-label^="Request access to "]' + ) + ) + for (const row of rows) { + act(() => + document.activeElement!.dispatchEvent( + new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }) + ) + ) + expect(document.activeElement).toBe(row) + } + expect(document.activeElement?.getAttribute('aria-label')).toBe('Request access to Locked tool') + expect(addBlock).not.toHaveBeenCalled() + }) + + it('restores existing hiding when requests are off', () => { + discovery.mockReturnValue({ data: { enabled: false } }) + act(() => root.render()) + expect(container.textContent).not.toContain('Access required') + expect(container.textContent).not.toContain('Locked') + }) +}) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.tsx index 1dd0e4634b1..a8f926f9d7b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/toolbar/toolbar.tsx @@ -21,9 +21,11 @@ import { Info, OverflowText, } from '@sim/emcn' -import { ChevronDown, Search } from '@sim/emcn/icons' +import { ChevronDown, Lock, Search } from '@sim/emcn/icons' import { useParams } from 'next/navigation' import { usePostHog } from 'posthog-js/react' +import { useWorkspaceAccessRequestFeatures } from '@/components/access-requests/permission-access-boundary' +import { RequestAccessModal } from '@/components/access-requests/request-access-action' import { captureEvent } from '@/lib/posthog/client' import { getTriggersForSidebar, hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils' import { @@ -53,6 +55,11 @@ interface BlockItem { icon?: ComponentType<{ className?: string }> bgColor?: string docsLink?: string + restricted?: boolean +} + +interface RestrictedBlockItem extends BlockItem { + section: 'triggers' | 'blocks' | 'tools' } interface ToolbarItemProps { @@ -116,15 +123,16 @@ const ToolbarItem = memo(function ToolbarItem({
@@ -135,6 +143,7 @@ const ToolbarItem = memo(function ToolbarItem({ data-toolbar-item-icon='' /> + {item.restricted && }
) }) @@ -379,11 +388,13 @@ export const Toolbar = memo( const blockItemRefs = useRef>([]) const customBlockItemRefs = useRef>([]) const toolItemRefs = useRef>([]) + const restrictedItemRefs = useRef>([]) const triggerRefCallbacks = useRef void>>({}) const blockRefCallbacks = useRef void>>({}) const customBlockRefCallbacks = useRef void>>({}) const toolRefCallbacks = useRef void>>({}) + const restrictedRefCallbacks = useRef void>>({}) const getTriggerRefCallback = useCallback((index: number) => { if (!triggerRefCallbacks.current[index]) { @@ -421,8 +432,20 @@ export const Toolbar = memo( return toolRefCallbacks.current[index] }, []) + const getRestrictedRefCallback = (index: number) => { + if (!restrictedRefCallbacks.current[index]) { + restrictedRefCallbacks.current[index] = (el) => { + restrictedItemRefs.current[index] = el + } + } + return restrictedRefCallbacks.current[index] + } + const posthog = usePostHog() - const { filterBlocks } = usePermissionConfig() + const { filterBlocks, isBlockRequestable } = usePermissionConfig() + const accessRequests = useWorkspaceAccessRequestFeatures() + const accessRequestsEnabled = accessRequests.data?.enabled === true + const [requestedBlockType, setRequestedBlockType] = useState(null) const sandboxAllowedBlocks = useSandboxBlockConstraints() const expandedSections = useToolbarStore((state) => state.expandedSections) @@ -471,6 +494,11 @@ export const Toolbar = memo( const allTriggers = getTriggers(blockOverlayVersion) const allBlocks = getBlocks(blockOverlayVersion) const allTools = getTools(blockOverlayVersion) + const requestedBlock = requestedBlockType + ? (allTriggers.find((item) => item.type === requestedBlockType) ?? + allBlocks.find((item) => item.type === requestedBlockType) ?? + allTools.find((item) => item.type === requestedBlockType)) + : undefined // Published custom blocks are their own section. Exclude disabled blocks (still // resolvable so placed instances survive, but not offered for new placement) and @@ -502,6 +530,13 @@ export const Toolbar = memo( .sort((a, b) => a.name.localeCompare(b.name)) }, [customBlocksData, currentWorkflowId, fallbackIconUrl]) + const handleRequestItemClick = useCallback( + (type: string) => { + if (accessRequestsEnabled) setRequestedBlockType(type) + }, + [accessRequestsEnabled] + ) + const visibleTriggers = useMemo(() => { if (sandboxAllowedBlocks !== null) return [] return filterBlocks(allTriggers) @@ -525,6 +560,32 @@ export const Toolbar = memo( return permitted.filter((b) => sandboxAllowedBlocks.includes(b.type)) }, [filterBlocks, allTools, sandboxAllowedBlocks]) + const restrictedItems = useMemo((): RestrictedBlockItem[] => { + if (!accessRequestsEnabled) return [] + const categories = [ + { section: 'triggers', items: sandboxAllowedBlocks === null ? allTriggers : [] }, + { section: 'blocks', items: allBlocks }, + { section: 'tools', items: allTools }, + ] as const + return categories.flatMap(({ section, items }) => + items + .filter( + (item) => + !isCustomBlockType(item.type) && + isBlockRequestable(item.type) && + (sandboxAllowedBlocks === null || sandboxAllowedBlocks.includes(item.type)) + ) + .map((item) => ({ ...item, restricted: true, section })) + ) + }, [ + accessRequestsEnabled, + allTriggers, + allBlocks, + allTools, + isBlockRequestable, + sandboxAllowedBlocks, + ]) + const normalizedQuery = searchQuery.trim().toLowerCase() const isSearching = normalizedQuery.length > 0 @@ -552,6 +613,11 @@ export const Toolbar = memo( return visibleTools.filter((tool) => tool.name.toLowerCase().includes(normalizedQuery)) }, [visibleTools, isSearching, normalizedQuery]) + const filteredRestrictedItems = useMemo(() => { + if (!isSearching) return restrictedItems + return restrictedItems.filter((item) => item.name.toLowerCase().includes(normalizedQuery)) + }, [restrictedItems, isSearching, normalizedQuery]) + /** * Trim ref arrays to current filtered length to prevent stale refs from * polluting keyboard navigation when items disappear (search, sandbox). @@ -560,6 +626,7 @@ export const Toolbar = memo( blockItemRefs.current.length = filteredBlocks.length customBlockItemRefs.current.length = filteredCustomBlocks.length toolItemRefs.current.length = filteredTools.length + restrictedItemRefs.current.length = filteredRestrictedItems.length /** * Section expansion is derived during search (force-expand sections with @@ -596,11 +663,11 @@ export const Toolbar = memo( * If there's a query, keep search mode active so ArrowUp/Down navigation continues * to work after focus moves into the section lists. */ - const handleSearchBlur = useCallback(() => { - if (!searchQuery.trim()) { + const handleSearchBlur = (event: React.FocusEvent) => { + if (!searchQuery.trim() && !rootRef.current?.contains(event.relatedTarget)) { setIsSearchActive(false) } - }, [searchQuery]) + } const handleItemContextMenu = useCallback( (e: React.MouseEvent, type: string, isTrigger: boolean, docsLink?: string) => { @@ -652,11 +719,11 @@ export const Toolbar = memo( }, [isContextMenuOpen, closeContextMenu]) /** - * Keyboard navigation across the three sections. + * Keyboard navigation follows visible section order, ending with access requests. * * - Active only when the toolbar tab is active and search mode is on. * - Skips collapsed or empty sections so focus only lands on visible items. - * - ArrowDown traverses search → triggers → blocks → tools. + * - ArrowDown traverses search → triggers → blocks → custom blocks → tools → access required. * - ArrowUp moves backward; from the first item of the first visible section * it wraps back to the search input. */ @@ -671,7 +738,7 @@ export const Toolbar = memo( if (!toolbarRoot || !activeEl || !toolbarRoot.contains(activeEl)) return type SectionList = { - key: ToolbarSectionKey + key: ToolbarSectionKey | 'restricted' items: HTMLDivElement[] } @@ -688,6 +755,12 @@ export const Toolbar = memo( ? blockItemRefs.current.filter((el): el is HTMLDivElement => el !== null) : [], }, + { + key: 'customBlocks', + items: sectionExpanded.customBlocks + ? customBlockItemRefs.current.filter((el): el is HTMLDivElement => el !== null) + : [], + }, { key: 'tools', items: sectionExpanded.tools @@ -695,6 +768,10 @@ export const Toolbar = memo( : [], }, ] + allSections.push({ + key: 'restricted', + items: restrictedItemRefs.current.filter((el): el is HTMLDivElement => el !== null), + }) const sections = allSections.filter((section) => section.items.length > 0) let sectionIndex = -1 @@ -769,6 +846,7 @@ export const Toolbar = memo( isSearchActive, sectionExpanded.triggers, sectionExpanded.blocks, + sectionExpanded.customBlocks, sectionExpanded.tools, ]) @@ -811,6 +889,16 @@ export const Toolbar = memo( + {requestedBlock && workspaceId && accessRequestsEnabled && ( + setRequestedBlockType(null)} + /> + )} + {/* Single scroll container with three collapsible sections */}
+ {filteredRestrictedItems.length > 0 && ( +
+
+ Access required + + Ask your organization admin to enable these blocks for your permission group. + +
+
+ {filteredRestrictedItems.map((item, index) => ( + + ))} +
+
+ )}
{/* Toolbar Item Context Menu */} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx index 7b77fb5678b..f1790e469f5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx @@ -31,6 +31,7 @@ import { useQueryClient } from '@tanstack/react-query' import { useParams, useRouter } from 'next/navigation' import { usePostHog } from 'posthog-js/react' import { useShallow } from 'zustand/react/shallow' +import { RequestAccessModal } from '@/components/access-requests/request-access-action' import { VariableIcon } from '@/components/icons' import { ThinkingLoader } from '@/components/ui' import { requestJson } from '@/lib/api/client/request' @@ -71,6 +72,7 @@ import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId] import { useWorkflowExecution } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution' import { getWorkflowLockToggleIds } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils' import { useDeleteWorkflow, useImportWorkflow } from '@/app/workspace/[workspaceId]/w/hooks' +import { useDiscoverAccessRequests } from '@/hooks/queries/access-requests' import { useCopilotChatSelection } from '@/hooks/queries/copilot-chat-selection' import { type CopilotChatListItem, @@ -219,6 +221,15 @@ export const Panel = memo(function Panel() { scope: usageLimitScope, isLoading: isUsageGateLoading, } = useUsageLimits({ workspaceId }) + const memberLimitRequest = useDiscoverAccessRequests( + { kind: 'workspace', workspaceId, targetKind: 'usage_limit', limit: 1, offset: 0 }, + usageExceeded && usageLimitScope === 'member' + ) + const [showLimitRequest, setShowLimitRequest] = useState(false) + const memberLimitTarget = + memberLimitRequest.isSuccess && memberLimitRequest.data.enabled + ? memberLimitRequest.data.entries.find((entry) => entry.state === 'requestable') + : undefined // Workflow execution hook const { handleRunWorkflow, handleCancelExecution, isExecuting } = useWorkflowExecution() @@ -243,10 +254,19 @@ export const Panel = memo(function Panel() { /** * Runs the workflow with usage limit check */ - const runWorkflow = useCallback(async () => { + const runWorkflow = async () => { if (isUsageGateLoading) return if (usageExceeded) { + if (usageLimitScope === 'member' && memberLimitTarget) { + if (memberLimitTarget.pendingRequestId) { + const params = new URLSearchParams({ requestId: memberLimitTarget.pendingRequestId }) + router.push(`/workspace/${encodeURIComponent(workspaceId)}/access-requests?${params}`) + } else { + setShowLimitRequest(true) + } + return + } const action = getWorkspaceUsageLimitAction(hostContext, session?.user?.id, { message: usageLimitMessage, scope: usageLimitScope, @@ -259,15 +279,7 @@ export const Panel = memo(function Panel() { return } await handleRunWorkflow() - }, [ - usageExceeded, - usageLimitMessage, - usageLimitScope, - isUsageGateLoading, - hostContext, - session?.user?.id, - handleRunWorkflow, - ]) + } // Chat state const { isChatOpen, setIsChatOpen } = useChatStore( @@ -708,6 +720,14 @@ export const Panel = memo(function Panel() { return ( <> + {showLimitRequest && memberLimitTarget && ( + setShowLimitRequest(false)} + /> + )}
+ + + {DIMENSION_LABELS[dimension]} + {!isMember && ( + + Workflow runs + + )} + {(isMember || dimension === 'workspace') && ( + + Chat runs + + )} + {!isMember && ( + <> + {dimension !== 'workspace' && ( + + Failed + + )} + + Failure rate + + + Avg. duration + + + )} + + + + {rows.map((row) => ( + + + {dimension === 'workspace' && row.workspaceId ? ( + { + if (row.workspaceId) onSelectWorkspace(row.workspaceId) + }} + className='max-w-full' + > + {row.label} + + ) : ( +
+ {isMember && } + +
+ )} + {dimension === 'workflow' && row.workspaceName && ( + + )} +
+ {!isMember && ( + + {row.workflowRuns.toLocaleString()} + + )} + {(isMember || dimension === 'workspace') && ( + + {row.chatRuns.toLocaleString()} + + )} + {!isMember && ( + <> + {dimension !== 'workspace' && ( + + {row.failed.toLocaleString()} + + )} + + {formatFailureRate(row.failureRate)} + + + {row.averageDurationMs === null + ? '—' + : row.averageDurationMs === 0 + ? '0 ms' + : formatDuration(row.averageDurationMs, { precision: 2 })} + + + )} +
+ ))} +
+
+ + ) +} diff --git a/apps/sim/ee/organization-usage/components/usage-consumers.tsx b/apps/sim/ee/organization-usage/components/usage-consumers.tsx index 6039688d960..a4b9342bdba 100644 --- a/apps/sim/ee/organization-usage/components/usage-consumers.tsx +++ b/apps/sim/ee/organization-usage/components/usage-consumers.tsx @@ -1,9 +1,8 @@ 'use client' import type { ComponentType } from 'react' -import { cn, disclosureChevronClass } from '@sim/emcn' +import { cn, disclosureChevronClass, formatChartCompactNumber } from '@sim/emcn' import { ArrowRight, ChevronDown } from '@sim/emcn/icons' -import { formatChartCompactNumber } from '@/components/charts' import { AnthropicIcon, AzureIcon, diff --git a/apps/sim/ee/organization-usage/components/usage-monitoring.tsx b/apps/sim/ee/organization-usage/components/usage-monitoring.tsx index 648fae039b4..23f4d1369d1 100644 --- a/apps/sim/ee/organization-usage/components/usage-monitoring.tsx +++ b/apps/sim/ee/organization-usage/components/usage-monitoring.tsx @@ -26,6 +26,8 @@ import { import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel' import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' import { serializeAuditLogFilters } from '@/ee/audit-logs/search-params' +import { ActivityPanel } from '@/ee/organization-usage/components/activity-panel' +import { OrganizationActivityOverview } from '@/ee/organization-usage/components/activity-summary' import { UsageConsumers } from '@/ee/organization-usage/components/usage-consumers' import { UsageSourceMix } from '@/ee/organization-usage/components/usage-source-mix' import { UsageSummary } from '@/ee/organization-usage/components/usage-summary' @@ -51,11 +53,6 @@ const TABS = USAGE_TAB_ORDER.map((tab) => ({ value: tab, label: USAGE_TAB_LABELS const DAY_MS = 24 * 60 * 60 * 1000 -/** - * One labelled band per view. The unit lives here rather than on every row — ten rows - * each ending in the word "credits" is noise, and a column header is where a reader - * already looks for it. - */ function UsageSection({ dimension, unit, @@ -86,14 +83,6 @@ interface UsageMonitoringProps { auditLogsHref: string } -/** - * Organization usage monitoring. - * - * The panel reads as one question per tab: how much and what kind of work - * (Overview), then who (Members), where (Workspaces), and on what (Models, BYOK). - * Only the visible tab's dimension is fetched, which is also the performance story — - * half the dimensions heap-scan the ledger, and a tab nobody opens never pays for one. - */ export function UsageMonitoring({ organizationId, eventsHref: eventsBaseHref, @@ -105,40 +94,17 @@ export function UsageMonitoring({ useUsageWindow() const [datePickerOpen, setDatePickerOpen] = useState(false) const [isExporting, setIsExporting] = useState(false) - /** The member whose credit limit is being edited, or null when the modal is closed. */ const [creditsTarget, setCreditsTarget] = useState(null) const isOverview = tab === USAGE_OVERVIEW_TAB - /** - * A selected workspace turns the Workspaces tab into that workspace's workflows — - * but only once the id resolves against the loaded list. A bookmarked id for a - * deleted workspace, or one belonging to another organization, would otherwise open - * a detail view with an untitled header and empty sections. Falling back to the - * list is the rule for every deep-linked entity id (`sim-url-state.md`); the - * lingering param is harmless. - */ + /** Resolve bookmarked workspace IDs before opening the credit drill-down. */ const isWorkspaceSelected = tab === 'workspace' && Boolean(workspace) - /** - * Per-member caps are hosted-only: the usage-limit route 404s where Sim does not - * own billing, and there is no enforcement to hang a cap off. This panel is the - * one organization surface a self-hosted enterprise can reach — Members is - * `requiresHosted` with no self-hosted override — so without this the menu would - * offer an action that could only fail. - */ + /** Member credit caps are enforced only on hosted deployments. */ const canManageCredits = tab === 'member' && hosted - const summary = useOrganizationUsageSummary(organizationId, window) - /** - * Kept alive in the drill-down purely to name it. The rule is to store the id and - * derive the entity from the loaded list. - * - * Pinned to the full page rather than to the panel's current row limit: the id can - * come from an expanded list or from a bookmark, and a lookup that only held the - * top ten resolved nothing for either — which reads as the drill-down refusing to - * open, since `isWorkspaceDetail` gates on the name. Requesting the ceiling means a - * click from an expanded list is served from that list's own cache entry. - */ + const summary = useOrganizationUsageSummary(organizationId, window, { enabled: isOverview }) + /** Use the full workspace page to resolve IDs selected from an expanded list. */ const workspaceList = useOrganizationUsageBreakdown(organizationId, window, 'workspace', { enabled: isWorkspaceSelected, limit: EXPANDED_ROW_COUNT, @@ -151,11 +117,12 @@ export function UsageMonitoring({ const isWorkspaceDetail = isWorkspaceSelected && (workspaceList.isLoading || workspaceName !== undefined) - const dimension: UsageBreakdownDimension = isOverview - ? 'source' - : isWorkspaceDetail - ? 'workflow' - : (tab as UsageBreakdownDimension) + const dimension: UsageBreakdownDimension = + isOverview || tab === 'activity' + ? 'source' + : isWorkspaceDetail + ? 'workflow' + : (tab as UsageBreakdownDimension) /** * Per breakdown, not per page: the drill-down shows two lists at once, so opening @@ -164,17 +131,14 @@ export function UsageMonitoring({ const rowLimitFor = (target: UsageBreakdownDimension) => expanded.includes(target) ? EXPANDED_ROW_COUNT : COLLAPSED_ROW_COUNT - /** - * Opens one list's tail, unless it is already at the API's ceiling — past that the - * `Other` row is a true remainder and the control would do nothing. `undefined` - * rather than a no-op handler, so the row renders as text instead of as a button. - */ + /** Offer expansion only while the API can return additional rows. */ const expandOtherFor = (target: UsageBreakdownDimension) => rowLimitFor(target) < EXPANDED_ROW_COUNT ? () => void setState({ expanded: [...expanded, target] }) : undefined const breakdown = useOrganizationUsageBreakdown(organizationId, window, dimension, { + enabled: tab !== 'activity', limit: rowLimitFor(dimension), ...(isWorkspaceDetail && workspace ? { workspaceId: workspace } : {}), }) @@ -183,41 +147,19 @@ export function UsageMonitoring({ limit: rowLimitFor('source'), ...(workspace ? { workspaceId: workspace } : {}), }) - /** - * The same headline and trend the Overview draws, narrowed to this workspace. - * - * A second summary rather than a figure derived from the lists below it: they carry - * totals but no time series, and the shape of the period is the question the chart - * answers. It is also the only place the drill-down states its window, which is why - * its section is labelled with the period rather than with the word "Usage". - */ + const workspaceSummary = useOrganizationUsageSummary(organizationId, window, { enabled: isWorkspaceDetail, ...(workspace ? { workspaceId: workspace } : {}), }) - // Already cached by Members and Billing, so the meter costs nothing extra and - // cannot report a different allowance than they do. - const billing = useOrganizationBilling(organizationId) + const billing = useOrganizationBilling(organizationId, { + enabled: isOverview && preset === 'current-period', + }) - /** - * The organization audit feed, narrowed to the workspace being drilled into. - * - * Only offered where that section exists. Usage and Audit logs carry the same - * hosted and enterprise gates, so reaching this panel already proves both — but - * their self-hosted overrides are separate flags, and an install with usage - * monitoring on and audit logs off would have been handed an action pointing at a - * section it had switched off. The window is deliberately not carried across: the - * audit feed speaks in rolling ranges (`Past 30 days`) and this panel in billing - * periods, so there is no honest mapping for `current-period`. - */ + /** Audit logs have a separate deployment flag and incompatible period presets. */ const auditLogsHref = hosted || features.auditLogs ? serializeAuditLogFilters(auditLogsBaseHref, { workspace }) : null - /** - * The drill-down is the same window, in more detail. Without the params it read its - * own defaults and silently showed the current period while the panel behind it - * showed a custom range — two pages disagreeing about what "this" means. - */ const eventsHref = serializeOrganizationUsageParams(eventsBaseHref, { preset: window.preset, startDate: window.startDate ?? null, @@ -229,16 +171,15 @@ export function UsageMonitoring({ setDatePickerOpen(true) return } - void setState({ preset: value as typeof preset, startDate: null, endDate: null }) + void setState({ + preset: value as typeof preset, + startDate: null, + endDate: null, + activityPage: 0, + }) } const handleDateRangeApply = (nextStart: string, nextEnd: string) => { - /** - * Refuse an over-long range here rather than committing it and letting all four - * reads fail. The server still enforces the cap — this is the same rule stated - * where the user can act on it, with the picker left open on the selection that - * needs changing. - */ const spanDays = Math.ceil( (new Date(nextEnd).getTime() - new Date(nextStart).getTime()) / DAY_MS ) @@ -246,15 +187,13 @@ export function UsageMonitoring({ toast.error(`Select a range of ${MAX_CUSTOM_RANGE_DAYS} days or fewer`) return } - void setState({ preset: 'custom', startDate: nextStart, endDate: nextEnd }) + void setState({ preset: 'custom', startDate: nextStart, endDate: nextEnd, activityPage: 0 }) setDatePickerOpen(false) } const handleExport = async () => { if (isExporting) return setIsExporting(true) - // The organization is the path segment below; the query no longer carries a - // second copy of it. const params = new URLSearchParams({ preset: window.preset, timezone: window.timezone, @@ -262,11 +201,6 @@ export function UsageMonitoring({ if (window.startDate) params.set('startDate', window.startDate) if (window.endDate) params.set('endDate', window.endDate) - /** - * Wrapped because the action is fire-and-forget: `onSelect` cannot await this, so - * a rejection — a dropped connection, a blob read that fails — became an unhandled - * promise and the button appeared to do nothing at all. - */ try { // boundary-raw-fetch: downloads a CSV blob and reads X-Export-Truncated before saving — a plain anchor navigation can do neither const response = await fetch( @@ -296,15 +230,10 @@ export function UsageMonitoring({ } } - /** - * The drill-down is a detail view, so it takes over the header: a back chip out of - * it, and the one action that belongs to a workspace rather than the organization. - */ if (isWorkspaceDetail && workspace) { return ( /logs`. Organization admin is not workspace - membership, and `WorkspaceLayout` answers a non-member with - `WorkspaceAccessDenied`, so the run-logs route was a one-way trip - to a dead end for any workspace the admin had not joined. Audit - logs live in the settings section the admin is already inside. - */ + /** Organization admins may lack workspace membership, so link to organization audit logs. */ text: 'Open logs', onSelect: () => router.push(auditLogsHref), onPrefetch: () => router.prefetch(auditLogsHref), @@ -331,16 +253,7 @@ export function UsageMonitoring({ : [] } > - {/* - Labelled with the period, not "Usage": the picker lives on the list behind - this view, so once you are in here the window is carried but invisible — and - a total with no stated period is a number people read as all-time. The - heading the chart already needs is where that belongs. - - No allowance passed, unlike the Overview: the limit is pooled across the - whole organization, and printing it under one workspace's figure would read - as that workspace's own cap. - */} + {/** Organization allowances do not apply to a single workspace. */} - {/* - Sources first, because in most workspaces the majority of usage is Chat - rather than workflow runs — and a workflow list alone hid that behind a - single unexplained row. Sources reconciles to the workspace total; Workflows - is explicitly the workflow-run subset of it. - */} + ) } @@ -392,27 +304,28 @@ export function UsageMonitoring({ text: 'Export', icon: Download, onSelect: () => void handleExport(), - disabled: summary.isLoading || isExporting, + disabled: isExporting, }, ]} > -
- - void setState({ tab: value as UsageTab, workspace: null, expanded: null }) - } - /> +
+
+ + void setState({ + tab: value as UsageTab, + workspace: null, + expanded: null, + activityPage: 0, + }) + } + /> +
- {/* ChipCombobox (Radix Popover, non-modal), not ChipSelect (Radix - DropdownMenu, modal by default) — a modal trigger closing in the - same tick that opens the Calendar popover below traps it behind - the modal's focus lock, so "Custom range" silently does nothing. */} + {/** A non-modal picker lets the calendar open without a competing focus lock. */} - {/* - No `showTime`: the panel buckets by calendar day, so a time of day is - precision it cannot render. It also emitted the end bound as an - inclusive `…T23:59:59` local wall time, which the window resolver then - treated as a midnight and pushed a further 24h — every custom range - covered an extra day, and a legal 92-day pick measured 93 and was - rejected. Bare `YYYY-MM-DD` bounds parse as UTC midnight, matching the - rest of the window logic. - */} + {/** Calendar-day bounds stay date-only; the server makes the end exclusive. */} - {/* - The allowance is a per-billing-period figure, so it is only comparable - to the current period's total. Against a rolling window or a custom - range it measures a different span than the limit covers — a 30-day - window spanning two periods could read "Over limit" while neither - period was — so those windows show the figure without an allowance. - */} + {/** Compare the allowance only with its billing period. */} - {/* - "What kind of work was this?" belongs beside the total it explains, not - behind a tab — it is the second half of the same sentence. - - One section, two readings of it: the list ranks the sources, the web shows - whether spend is concentrated or spread. Two `SettingsSection`s side by - side would have drawn two half-width hairlines on one line — every other - rule in this panel spans the column — and left one header carrying the - `credits` unit while its neighbour, showing the same data, carried none. - - `auto-fit` on a track minimum rather than a `lg:` breakpoint: the settings - content column is a fixed `max-w-[48rem]`, so viewport width says nothing - about how wide this actually is. Same rule as `RESOURCE_LIST_GRID`. - */} + - {/* - `min(320px, 100%)` rather than a bare `320px`: a track minimum is a - hard floor, so on a column narrower than the minimum the grid would - be wider than its container and overflow. Capping the floor at the - available width collapses it to one column instead. - */} -
- - -
+
+ ) : tab === 'activity' ? ( + ) : ( void setState({ workspace: row.id, expanded: null }, { history: 'push' }), } @@ -544,14 +412,7 @@ export function UsageMonitoring({ )} - {/* - A sibling of the panel, not a child. `SettingsPanel` renders its children - straight into the shell's gap-7 content column, so a modal mounted inside it - is a body slot that contributes to that spacing. - - The same modal the Members settings page opens, driven by the same hooks — - setting a cap here and there is one implementation, not two. - */} + {/** Keep the modal outside the panel’s content-spacing layout. */} {canManageCredits && ( > = { + workflow: 'var(--indicator-seat-filled)', + 'sim-chat': 'var(--brand-agent)', + mcp_copilot: 'var(--badge-purple-text)', + mothership_block: 'var(--badge-pink-text)', + 'knowledge-base': 'var(--badge-teal-text)', + enrichment: 'var(--badge-amber-text)', + wand: 'var(--badge-cyan-text)', + 'voice-input': 'var(--badge-orange-text)', + 'voice-output': 'var(--text-success)', + 'api-tool': 'var(--badge-blue-text)', +} +const MAX_SOURCES = 5 interface UsageSourceMixProps { breakdown?: OrganizationUsageBreakdown @@ -28,73 +23,28 @@ interface UsageSourceMixProps { isError: boolean } -/** - * The source list's shape, beside the list itself. - * - * The rows answer "how much did each source cost"; they cannot answer "is this - * organization's spend concentrated or spread", which is the question an admin - * actually opens this tab with. Reading the same rows as a polygon makes a single - * dominant source and an even split visibly different at a glance. - */ export function UsageSourceMix({ breakdown, isLoading, isError }: UsageSourceMixProps) { - /* - Stabilized so `RadarChart`'s `memo()` can pass — built inline it was a new array - on every render of the panel. - */ - const axes = useMemo(() => { - const rows = breakdown?.rows ?? [] - const head = rows.slice(0, MAX_AXES) - const tail = rows.slice(MAX_AXES) - /* - The folded axis carries the API's own remainder as well as the rows this chart - dropped, so the web reconciles to the same total as the list beside it. - - It is deliberately *not* labelled `Other (N more)`: the chart folds at MAX_AXES - and the list folds at COLLAPSED_ROW_COUNT, so the two counts genuinely differ, - and printing both a few pixels apart under identical wording reads as a bug. The - count moves into the hover row, where it is attributed. - */ - const otherRowCount = tail.length + (breakdown?.other.rowCount ?? 0) - const otherCredits = - tail.reduce((total, row) => total + row.credits, 0) + (breakdown?.other.credits ?? 0) - return [ - ...head.map((row) => ({ - label: row.label, - value: row.credits, - display: row.credits.toLocaleString(), - })), - ...(otherRowCount > 0 - ? [ - { - label: 'Other', - value: otherCredits, - display: `${otherCredits.toLocaleString()} · ${otherRowCount} sources`, - }, - ] - : []), - ] - }, [breakdown]) - - if (isError) { - return ( - - Couldn't load this view. - - ) - } - if (isLoading || !breakdown) { - return Loading… - } - - /* - The chart refuses fewer than three axes — a two-gon is a line, not a distribution — - but its own fallback is a `height`-tall "No data" box, which beside a list holding - two populated rows says the wrong thing at the wrong size. The wrapper answers - instead, in the same inline empty state its neighbour uses. - */ - if (axes.length < 3) { - return Not enough sources to compare. - } - - return + const rows = breakdown?.rows ?? [] + const head = rows.slice(0, MAX_SOURCES) + const tail = rows.slice(MAX_SOURCES) + const otherCredits = + tail.reduce((total, row) => total + row.credits, 0) + (breakdown?.other.credits ?? 0) + const segments = [ + ...head.map((row) => ({ + label: row.label, + value: row.credits, + color: SOURCE_COLORS[row.id ?? ''] ?? 'var(--text-muted)', + })), + ...(otherCredits > 0 ? [{ label: 'Other', value: otherCredits, color: 'var(--border)' }] : []), + ] + + return ( + + + + ) } diff --git a/apps/sim/ee/organization-usage/components/usage-summary.test.tsx b/apps/sim/ee/organization-usage/components/usage-summary.test.tsx new file mode 100644 index 00000000000..d730a2f5abc --- /dev/null +++ b/apps/sim/ee/organization-usage/components/usage-summary.test.tsx @@ -0,0 +1,28 @@ +/** @vitest-environment node */ +import { renderToStaticMarkup } from 'react-dom/server' +import { describe, expect, it } from 'vitest' +import type { OrganizationUsageSummary } from '@/lib/api/contracts/organization-usage' +import { UsageSummary } from '@/ee/organization-usage/components/usage-summary' + +const summary: OrganizationUsageSummary = { + window: { start: '2026-01-01', end: '2026-01-08', source: 'range' }, + bucket: 'day', + totals: { credits: 200 }, + previousTotals: { credits: 100 }, + series: [], +} + +describe('UsageSummary', () => { + it('hides stale usage badges when a refresh fails', () => { + const render = (isError: boolean) => + renderToStaticMarkup( + + ) + expect(render(false)).toContain('Over limit') + expect(render(false)).toContain('compared with the previous period') + const failed = render(true) + expect(failed).not.toContain('Over limit') + expect(failed).not.toContain('compared with the previous period') + expect(failed).toContain('load credits.') + }) +}) diff --git a/apps/sim/ee/organization-usage/components/usage-summary.tsx b/apps/sim/ee/organization-usage/components/usage-summary.tsx index 41e5d5643dd..329ab208644 100644 --- a/apps/sim/ee/organization-usage/components/usage-summary.tsx +++ b/apps/sim/ee/organization-usage/components/usage-summary.tsx @@ -1,34 +1,18 @@ 'use client' import { useMemo } from 'react' -import { Badge, cn } from '@sim/emcn' -import { BarChart } from '@/components/charts' +import { Badge, BarChart, ChartFrame, cn } from '@sim/emcn' import type { OrganizationUsageSummary } from '@/lib/api/contracts/organization-usage' import { formatCreditsLabel } from '@/lib/billing/credits/conversion' -import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' - -/** Consumption, matching the seat meter's indicator rather than an outcome colour. */ -const USAGE_SERIES_COLOR = 'var(--indicator-seat-filled)' interface UsageSummaryProps { summary?: OrganizationUsageSummary - /** Pooled allowance in credits, from the organization's billing data. `null` when uncapped. */ limitCredits?: number | null isLoading: boolean isError: boolean - /** - * Dims the figures while a re-keyed fetch resolves, rather than blanking them — the - * same treatment `UsageConsumers` gives a retained list. Without it the headline and - * chart present the previous period's numbers as though they were the new period's. - */ isPlaceholderData?: boolean } -function percentDelta(current: number, previous: number): number | null { - if (previous <= 0) return null - return ((current - previous) / previous) * 100 -} - export function UsageSummary({ summary, limitCredits, @@ -36,72 +20,57 @@ export function UsageSummary({ isError, isPlaceholderData, }: UsageSummaryProps) { - /* - Stabilized so `BarChart`'s `memo()` can actually pass. Built inline it was a new - array on every render of the panel — a date-picker toggle or an export click - re-rendered ninety bars for nothing. - */ const series = useMemo( () => summary?.series.map((point) => ({ timestamp: point.timestamp, value: point.credits })) ?? [], [summary] ) - - if (isError) { - return ( - - Couldn't load usage. - - ) - } - if (isLoading || !summary) { - return Loading usage… - } - - const used = summary.totals.credits - const delta = summary.previousTotals ? percentDelta(used, summary.previousTotals.credits) : null + const used = !isError ? (summary?.totals.credits ?? 0) : 0 + const previous = !isError ? (summary?.previousTotals?.credits ?? 0) : 0 + const delta = previous > 0 ? ((used - previous) / previous) * 100 : null const hasLimit = limitCredits != null && limitCredits > 0 - const isOverLimit = hasLimit && used > limitCredits - return ( -
- {/* - One line, and the allowance sits beside the figure rather than under it — - restating "4,958 credits used" below a "4,958 credits" headline said the same - number twice and read as a rendering bug. - */} -
- {/* - `text-base`, not `text-lg`: the shell's page title is `text-lg`, and a - metric drawn at the same size competed with the header for the first read. - */} - - {formatCreditsLabel(used)} - - {hasLimit && ( - // Bare number, not `formatCreditsLabel`: the headline beside it already - // names the unit, and "4,958 credits of 200,000 credits" says it twice. - - of {limitCredits.toLocaleString()} +
+
+
+ + {isError || !summary ? '—' : formatCreditsLabel(used)} - )} - {delta !== null && ( - 0 ? 'amber' : 'gray-secondary'} size='sm'> - {`${delta > 0 ? '↑' : '↓'} ${Math.abs(delta).toFixed(0)}% vs last period`} - - )} - {isOverLimit && ( - // `red`, not `amber`: past the pooled allowance is a violation, and the - // trend badge sitting immediately beside it is already amber. - - Over limit - - )} + {hasLimit && ( + + of {limitCredits.toLocaleString()} + + )} +
+
+ {delta !== null && ( + 0 ? 'amber' : 'gray-secondary'} + size='sm' + aria-label={`${Math.abs(delta).toFixed(0)}% ${delta > 0 ? 'increase' : delta < 0 ? 'decrease' : 'change'} compared with the previous period`} + >{`${delta > 0 ? '↑' : '↓'} ${Math.abs(delta).toFixed(0)}%`} + )} + {hasLimit && used > limitCredits && ( + + Over limit + + )} +
- - + + +
) } diff --git a/apps/sim/ee/organization-usage/constants.ts b/apps/sim/ee/organization-usage/constants.ts index 510933e6485..9f2e08e3c7d 100644 --- a/apps/sim/ee/organization-usage/constants.ts +++ b/apps/sim/ee/organization-usage/constants.ts @@ -25,19 +25,22 @@ export const PERIOD_OPTIONS: ComboboxOption[] = USAGE_WINDOW_PRESETS.map((preset })) export const USAGE_OVERVIEW_TAB = 'overview' as const -export type UsageTab = typeof USAGE_OVERVIEW_TAB | 'member' | 'workspace' | 'model' | 'byok' +export type UsageTab = + | typeof USAGE_OVERVIEW_TAB + | 'activity' + | 'member' + | 'workspace' + | 'model' + | 'byok' /** - * The panel reads as one question per tab, in the order an admin asks them: - * how much (Overview, which also answers *what kind* via its source mix), then who, - * then where, then on what. - * - * Workflows is deliberately not a tab. A workflow is only meaningful inside its - * workspace, and a flat org-wide workflow list is dominated by a bucket of usage that - * has no workflow at all — so it lives as the Workspaces drill-down instead. + * Activity ranks retained executions; the member, workspace, and model tabs rank + * ledger spend. Workflow spend remains within the workspace drill-down because + * charges from other sources do not carry workflow attribution. */ export const USAGE_TAB_ORDER: readonly UsageTab[] = [ USAGE_OVERVIEW_TAB, + 'activity', 'member', 'workspace', 'model', @@ -51,6 +54,7 @@ export const USAGE_TAB_ORDER: readonly UsageTab[] = [ export const USAGE_TAB_LABELS: Record = { overview: 'Overview', + activity: 'Activity', member: 'Members', workspace: 'Workspaces', model: 'Models', diff --git a/apps/sim/ee/organization-usage/hooks/use-usage-window.ts b/apps/sim/ee/organization-usage/hooks/use-usage-window.ts index a10ac5251b4..27c48c500c1 100644 --- a/apps/sim/ee/organization-usage/hooks/use-usage-window.ts +++ b/apps/sim/ee/organization-usage/hooks/use-usage-window.ts @@ -5,6 +5,7 @@ import { MAX_CUSTOM_RANGE_DAYS, type UsageWindowPreset, } from '@/lib/api/contracts/organization-usage' +import { ACTIVITY_MAX_PAGE } from '@/lib/billing/core/organization-activity' import { formatDateShort } from '@/lib/core/utils/date-display' import { getBrowserTimezone } from '@/lib/core/utils/timezone' import { DEFAULT_USAGE_PRESET, PERIOD_LABELS } from '@/ee/organization-usage/constants' @@ -22,15 +23,7 @@ function isCalendarDate(value: string | null): value is string { return new Date(`${value}T00:00:00.000Z`).toISOString().slice(0, 10) === value } -/** - * Every rule the window resolver enforces, checked here too. - * - * The server refuses an unreal date, an inverted pair, and a span past the cap — each - * as a 400. A deep link carrying any of them would otherwise be marked "resolved" and - * fail all four queries on the page, which is a worse outcome than the fallback this - * guard exists to provide. Duplicated deliberately, and narrowly: these are the three - * conditions that turn a link into an error rather than into different data. - */ +/** Match server date validation so invalid links fall back before issuing requests. */ export function isUsableCustomRange(start: string | null, end: string | null): boolean { if (!isCalendarDate(start) || !isCalendarDate(end)) return false const from = new Date(`${start}T00:00:00.000Z`).getTime() @@ -39,34 +32,16 @@ export function isUsableCustomRange(start: string | null, end: string | null): b return Math.round((to - from) / DAY_MS) + 1 <= MAX_CUSTOM_RANGE_DAYS } -/** - * The panel's URL state, resolved into the window every query is keyed on. - * - * A `custom` preset missing either bound falls back to the default rather than - * querying unbounded — the same partial-deep-link guard audit-logs uses. - */ +/** Resolve shared URL filters, falling back when a custom range is invalid. */ export function useUsageWindow() { const [state, setState] = useQueryStates(organizationUsageParsers, organizationUsageUrlKeys) const timezone = getBrowserTimezone() - /** - * Both bounds present, and a range the API will actually accept. - * - * Every condition the window resolver refuses with a 400 — an unreal date, an - * inverted pair, a span past the cap — has to be checked here too, or a bookmarked - * link carrying one is marked resolved and fails all four queries on the page. The - * fallback exists precisely so a bad link degrades to the default window instead. - */ const isResolvedCustom = state.preset === 'custom' && isUsableCustomRange(state.startDate, state.endDate) const preset: UsageWindowPreset = state.preset === 'custom' && !isResolvedCustom ? DEFAULT_USAGE_PRESET : state.preset - /* - Not memoized: this object is only ever hashed, never compared by identity — - React Query hashes a query key structurally, and the panel reads the primitive - fields off it directly. - */ const window: OrganizationUsageWindowKey = { preset, ...(isResolvedCustom @@ -83,13 +58,10 @@ export function useUsageWindow() { window, tab: state.tab, workspace: state.workspace, + activityDimension: state.activityDimension, + activitySort: state.activitySort, + activityPage: Math.min(ACTIVITY_MAX_PAGE, Math.max(0, state.activityPage)), expanded: state.expanded, - /** - * The *resolved* preset, not the raw URL value. A partial custom deep link - * queries the current period, so surfacing `state.preset` left the picker - * reading "Custom range" over data that was not custom — and the allowance - * gate, which keys on `current-period`, disagreed with the window too. - */ preset, startDate: state.startDate, endDate: state.endDate, diff --git a/apps/sim/ee/organization-usage/search-params.ts b/apps/sim/ee/organization-usage/search-params.ts index c8a74d44e17..201ab5a7da9 100644 --- a/apps/sim/ee/organization-usage/search-params.ts +++ b/apps/sim/ee/organization-usage/search-params.ts @@ -1,8 +1,15 @@ -import { createSerializer, parseAsArrayOf, parseAsString, parseAsStringLiteral } from 'nuqs/server' +import { + createSerializer, + parseAsArrayOf, + parseAsInteger, + parseAsString, + parseAsStringLiteral, +} from 'nuqs/server' import { USAGE_BREAKDOWN_DIMENSIONS, USAGE_WINDOW_PRESETS, } from '@/lib/api/contracts/organization-usage' +import { ACTIVITY_DIMENSIONS, ACTIVITY_SORTS } from '@/lib/billing/core/organization-activity' import { parseAsDateString } from '@/app/workspace/[workspaceId]/logs/search-params' import { DEFAULT_USAGE_PRESET, @@ -10,35 +17,18 @@ import { USAGE_TAB_ORDER, } from '@/ee/organization-usage/constants' -/** - * URL state for the organization usage panel. - * - * `startDate`/`endDate` are deliberately nullable (no `.withDefault`): they exist only - * while `preset` is `custom`. Every other preset derives its window server-side from - * the organization's subscription period, so a default here would be meaningless — and - * worse, would silently pin the window to a stale date. - */ +/** Custom dates stay nullable because other presets resolve their bounds server-side. */ export const organizationUsageParsers = { preset: parseAsStringLiteral(USAGE_WINDOW_PRESETS).withDefault(DEFAULT_USAGE_PRESET), startDate: parseAsDateString, endDate: parseAsDateString, tab: parseAsStringLiteral(USAGE_TAB_ORDER).withDefault(DEFAULT_USAGE_TAB), - /** - * Nullable by design: only the id is stored, and the detail view opens only once it - * resolves against the loaded list — a stale id from an old link falls back to the - * list rather than rendering an empty drill-down. - */ + workspace: parseAsString, - /** - * Which breakdowns have had their `Other` row opened, named by dimension. In the URL - * because it is shareable view-state like every other filter here — and because it - * changes which rows the page fetched, so a shared link that omitted it would not - * show the list the sender was looking at. - * - * A list, not a flag: the workspace drill-down renders two breakdowns at once, and - * one boolean meant opening either tail silently opened the other's — refetching a - * list nobody asked to expand, and leaving its `Other` row as inert text. - */ + activityDimension: parseAsStringLiteral(ACTIVITY_DIMENSIONS).withDefault('workspace'), + activitySort: parseAsStringLiteral(ACTIVITY_SORTS).withDefault('runs'), + activityPage: parseAsInteger.withDefault(0), + /** Track expanded dimensions separately because workspace detail contains multiple lists. */ expanded: parseAsArrayOf(parseAsStringLiteral(USAGE_BREAKDOWN_DIMENSIONS)).withDefault([]), } as const @@ -50,15 +40,13 @@ export const organizationUsageUrlKeys = { urlKeys: { startDate: 'start-date', endDate: 'end-date', + activityDimension: 'activity-group', + activitySort: 'activity-sort', + activityPage: 'activity-page', }, } as const -/** - * Outbound links into the usage drill-downs, serialized from the same parser map the - * destination reads. Hand-writing the wire keys duplicated the `urlKeys` remap, so - * renaming `start-date` would have silently dropped the window from every such link — - * exactly the panel/drill-down disagreement the events href exists to prevent. - */ +/** Serialize links with the destination’s parser map and URL keys. */ export const serializeOrganizationUsageParams = createSerializer(organizationUsageParsers, { clearOnDefault: true, urlKeys: organizationUsageUrlKeys.urlKeys, diff --git a/apps/sim/hooks/queries/organization-activity.ts b/apps/sim/hooks/queries/organization-activity.ts new file mode 100644 index 00000000000..34da2fe06cb --- /dev/null +++ b/apps/sim/hooks/queries/organization-activity.ts @@ -0,0 +1,91 @@ +import { useQuery } from '@tanstack/react-query' +import { requestJson } from '@/lib/api/client/request' +import { + getOrganizationActivityBreakdownContract, + getOrganizationActivitySummaryContract, +} from '@/lib/api/contracts/organization-activity' +import type { ActivityDimension, ActivitySort } from '@/lib/billing/core/organization-activity' +import { + type OrganizationUsageWindowKey, + organizationUsageKeys, +} from '@/hooks/queries/utils/organization-usage-keys' + +export const ORGANIZATION_ACTIVITY_STALE_TIME = 60 * 1000 + +export const organizationActivityKeys = { + all: (organizationId: string) => + [...organizationUsageKeys.all(organizationId), 'activity'] as const, + summaries: (organizationId: string) => + [...organizationActivityKeys.all(organizationId), 'summary'] as const, + summary: (organizationId: string, window: OrganizationUsageWindowKey, workspaceId?: string) => + [...organizationActivityKeys.summaries(organizationId), window, workspaceId ?? ''] as const, + breakdowns: (organizationId: string) => + [...organizationActivityKeys.all(organizationId), 'breakdown'] as const, + breakdown: ( + organizationId: string, + window: OrganizationUsageWindowKey, + dimension: ActivityDimension, + sort: ActivitySort, + page: number, + workspaceId?: string + ) => + [ + ...organizationActivityKeys.breakdowns(organizationId), + window, + workspaceId ?? '', + dimension, + sort, + page, + ] as const, +} + +interface ActivityQueryOptions { + enabled?: boolean + workspaceId?: string +} + +export function useOrganizationActivitySummary( + organizationId: string, + window: OrganizationUsageWindowKey, + { enabled = true, workspaceId }: ActivityQueryOptions = {} +) { + return useQuery({ + queryKey: organizationActivityKeys.summary(organizationId, window, workspaceId), + queryFn: ({ signal }) => + requestJson(getOrganizationActivitySummaryContract, { + params: { id: organizationId }, + query: { ...window, workspaceId }, + signal, + }), + enabled: Boolean(organizationId) && enabled, + staleTime: ORGANIZATION_ACTIVITY_STALE_TIME, + }) +} + +export function useOrganizationActivityBreakdown( + organizationId: string, + window: OrganizationUsageWindowKey, + dimension: ActivityDimension, + sort: ActivitySort, + page: number, + { enabled = true, workspaceId }: ActivityQueryOptions = {} +) { + return useQuery({ + queryKey: organizationActivityKeys.breakdown( + organizationId, + window, + dimension, + sort, + page, + workspaceId + ), + queryFn: ({ signal }) => + requestJson(getOrganizationActivityBreakdownContract, { + params: { id: organizationId }, + query: { ...window, workspaceId, dimension, sort, page }, + signal, + }), + enabled: Boolean(organizationId) && enabled, + staleTime: ORGANIZATION_ACTIVITY_STALE_TIME, + }) +} diff --git a/apps/sim/lib/api/contracts/organization-activity.test.ts b/apps/sim/lib/api/contracts/organization-activity.test.ts new file mode 100644 index 00000000000..65f9998ac4e --- /dev/null +++ b/apps/sim/lib/api/contracts/organization-activity.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest' +import { organizationActivityBreakdownQuerySchema } from '@/lib/api/contracts/organization-activity' + +describe('activity query boundaries', () => { + it.each([ + { dimension: 'transcripts' }, + { sort: 'arbitrary sql' }, + { page: -1 }, + { page: 1001 }, + { page: 1.5 }, + { timezone: 'invalid' }, + { startDate: '2026-02-30' }, + { endDate: '2026-08-01T23:59:59Z' }, + ])('rejects unsupported dimensions, orders, pages and dates: %j', (query) => { + expect(organizationActivityBreakdownQuerySchema.safeParse(query).success).toBe(false) + }) + + it('parses bounded URL parameters into the shared query', () => { + expect( + organizationActivityBreakdownQuerySchema.parse({ page: '2', dimension: 'workflow' }) + ).toMatchObject({ + page: 2, + dimension: 'workflow', + sort: 'runs', + preset: 'current-period', + timezone: 'UTC', + }) + }) +}) diff --git a/apps/sim/lib/api/contracts/organization-activity.ts b/apps/sim/lib/api/contracts/organization-activity.ts new file mode 100644 index 00000000000..23a63b6a2b3 --- /dev/null +++ b/apps/sim/lib/api/contracts/organization-activity.ts @@ -0,0 +1,76 @@ +import { z } from 'zod' +import { organizationUsageSummaryQuerySchema } from '@/lib/api/contracts/organization-usage' +import { organizationIdSchema, workspaceIdSchema } from '@/lib/api/contracts/primitives' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { + ACTIVITY_DIMENSIONS, + ACTIVITY_MAX_PAGE, + ACTIVITY_PAGE_SIZE, + ACTIVITY_SORTS, +} from '@/lib/billing/core/organization-activity' + +export const organizationActivityBreakdownQuerySchema = organizationUsageSummaryQuerySchema.extend({ + dimension: z.enum(ACTIVITY_DIMENSIONS).default('workspace'), + sort: z.enum(ACTIVITY_SORTS).default('runs'), + page: z.coerce.number().int().min(0).max(ACTIVITY_MAX_PAGE).default(0), +}) +export type OrganizationActivityBreakdownQuery = z.input< + typeof organizationActivityBreakdownQuerySchema +> + +export const organizationActivityMetricsSchema = z.object({ + workflowRuns: z.number().int().nonnegative(), + completed: z.number().int().nonnegative(), + failed: z.number().int().nonnegative(), + chatRuns: z.number().int().nonnegative(), + chatMembers: z.number().int().nonnegative(), + failureRate: z.number().min(0).max(1).nullable(), + averageDurationMs: z.number().nonnegative().nullable(), +}) + +export const organizationActivitySummarySchema = z.object({ + workspace: z.object({ id: workspaceIdSchema, name: z.string() }).nullable(), + totals: organizationActivityMetricsSchema, + series: z + .array( + z.object({ + timestamp: z.string(), + workflowRuns: z.number().int().nonnegative(), + chatRuns: z.number().int().nonnegative(), + failed: z.number().int().nonnegative(), + }) + ) + .max(1000), +}) +export type OrganizationActivitySummary = z.output + +export const organizationActivityBreakdownSchema = z.object({ + rows: z + .array( + organizationActivityMetricsSchema.extend({ + id: z.string(), + label: z.string(), + workspaceId: workspaceIdSchema.nullable(), + workspaceName: z.string().nullable(), + }) + ) + .max(ACTIVITY_PAGE_SIZE), + hasMore: z.boolean(), +}) +export type OrganizationActivityBreakdown = z.output + +export const getOrganizationActivitySummaryContract = defineRouteContract({ + method: 'GET', + path: '/api/organizations/[id]/usage/activity/summary', + params: z.object({ id: organizationIdSchema }), + query: organizationUsageSummaryQuerySchema, + response: { mode: 'json', schema: organizationActivitySummarySchema }, +}) + +export const getOrganizationActivityBreakdownContract = defineRouteContract({ + method: 'GET', + path: '/api/organizations/[id]/usage/activity/breakdown', + params: z.object({ id: organizationIdSchema }), + query: organizationActivityBreakdownQuerySchema, + response: { mode: 'json', schema: organizationActivityBreakdownSchema }, +}) diff --git a/apps/sim/lib/billing/application/organization-usage/get-organization-activity.test.ts b/apps/sim/lib/billing/application/organization-usage/get-organization-activity.test.ts new file mode 100644 index 00000000000..59aaaa23e16 --- /dev/null +++ b/apps/sim/lib/billing/application/organization-usage/get-organization-activity.test.ts @@ -0,0 +1,121 @@ +/** @vitest-environment node */ +import type { Principal, SessionPrincipal } from '@sim/auth/principal' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const mocks = vi.hoisted(() => ({ + authority: vi.fn(), + entitlement: vi.fn(), + subscription: vi.fn(), + summary: vi.fn(), + breakdown: vi.fn(), + workspace: vi.fn(), +})) + +vi.mock('@/lib/billing/core/workspace-billing-authority', () => ({ + canUserManageBillingEntity: mocks.authority, +})) +vi.mock('@/lib/billing/core/subscription', () => ({ + isOrganizationFeatureEntitled: mocks.entitlement, +})) +vi.mock('@/lib/billing/core/billing', () => ({ getOrganizationSubscription: mocks.subscription })) +vi.mock('@/lib/billing/core/organization-activity-queries', () => ({ + readActivitySummary: mocks.summary, + readActivityBreakdown: mocks.breakdown, + readActivityWorkspace: mocks.workspace, +})) + +import { + getOrganizationActivityBreakdown, + getOrganizationActivitySummary, +} from '@/lib/billing/application/organization-usage/get-organization-activity' +import { activityMetrics } from '@/lib/billing/core/organization-activity' + +const principal: SessionPrincipal = { kind: 'session', userId: 'admin', sessionId: 'session' } +const input = { + organizationId: 'org', + preset: 'custom' as const, + timezone: 'America/Los_Angeles', + startDate: new Date('2026-03-08'), + endDate: new Date('2026-03-09'), + workspaceId: 'workspace', +} +const breakdownInput = { + ...input, + dimension: 'workflow' as const, + sort: 'failures' as const, + page: 2, +} + +beforeEach(() => { + vi.clearAllMocks() + mocks.authority.mockResolvedValue(true) + mocks.entitlement.mockResolvedValue(true) + mocks.subscription.mockResolvedValue(null) + mocks.workspace.mockResolvedValue({ id: 'workspace', name: 'Support' }) + mocks.summary.mockResolvedValue({ totals: activityMetrics(), series: [] }) + mocks.breakdown.mockResolvedValue({ rows: [], hasMore: false }) +}) + +describe.each([ + [ + 'summary', + (actor: Principal) => getOrganizationActivitySummary.execute({ principal: actor, input }), + ], + [ + 'breakdown', + (actor: Principal) => + getOrganizationActivityBreakdown.execute({ principal: actor, input: breakdownInput }), + ], +] as const)('organization activity %s authorization', (_name, run) => { + it('rejects API keys before loading organization data', async () => { + await expect( + run({ kind: 'personal_api_key', userId: 'admin', keyId: 'key' }) + ).rejects.toMatchObject({ detailCode: 'PRINCIPAL_KIND_NOT_PERMITTED' }) + expect(mocks.authority).not.toHaveBeenCalled() + expect(mocks.workspace).not.toHaveBeenCalled() + }) + + it('requires current organization admin authority before checking entitlement or reading activity', async () => { + mocks.authority.mockResolvedValue(false) + await expect(run(principal)).rejects.toMatchObject({ + detailCode: 'ORGANIZATION_ADMIN_REQUIRED', + }) + expect(mocks.authority).toHaveBeenCalledWith({ type: 'organization', id: 'org' }, 'admin') + expect(mocks.entitlement).not.toHaveBeenCalled() + expect(mocks.workspace).not.toHaveBeenCalled() + expect(mocks.summary).not.toHaveBeenCalled() + expect(mocks.breakdown).not.toHaveBeenCalled() + }) + + it('enforces the enterprise or self-hosted entitlement', async () => { + mocks.entitlement.mockResolvedValue(false) + await expect(run(principal)).rejects.toMatchObject({ detailCode: 'ENTERPRISE_PLAN_REQUIRED' }) + expect(mocks.workspace).not.toHaveBeenCalled() + }) + + it('rejects a foreign or deleted workspace before any activity aggregation', async () => { + mocks.workspace.mockResolvedValue(null) + await expect(run(principal)).rejects.toThrow('Workspace not found') + expect(mocks.workspace).toHaveBeenCalledWith('org', 'workspace') + expect(mocks.summary).not.toHaveBeenCalled() + expect(mocks.breakdown).not.toHaveBeenCalled() + }) +}) + +it('uses the same authorized scope and timezone for summaries and paginated breakdowns', async () => { + const summary = await getOrganizationActivitySummary.execute({ principal, input }) + await getOrganizationActivityBreakdown.execute({ principal, input: breakdownInput }) + const scope = { + organizationId: 'org', + workspaceId: 'workspace', + start: new Date('2026-03-08T08:00:00Z'), + end: new Date('2026-03-10T07:00:00Z'), + } + expect(mocks.summary).toHaveBeenCalledWith(scope, 'day', 'America/Los_Angeles') + expect(mocks.breakdown).toHaveBeenCalledWith(scope, 'workflow', 'failures', 2) + expect(summary.workspace).toEqual({ id: 'workspace', name: 'Support' }) + expect(summary.series).toEqual([ + { timestamp: '2026-03-08T00:00:00', workflowRuns: 0, chatRuns: 0, failed: 0 }, + { timestamp: '2026-03-09T00:00:00', workflowRuns: 0, chatRuns: 0, failed: 0 }, + ]) +}) diff --git a/apps/sim/lib/billing/application/organization-usage/get-organization-activity.ts b/apps/sim/lib/billing/application/organization-usage/get-organization-activity.ts new file mode 100644 index 00000000000..730caf17697 --- /dev/null +++ b/apps/sim/lib/billing/application/organization-usage/get-organization-activity.ts @@ -0,0 +1,81 @@ +import { + type AuthorizedOrganizationUsageContext, + defineAuthorizedOrganizationUsageUseCase, +} from '@/lib/billing/application/organization-usage/authorized-organization-usage-use-case' +import type { OrganizationUsageSummaryInput } from '@/lib/billing/application/organization-usage/get-organization-usage-summary' +import { organizationUsageOperations } from '@/lib/billing/application/organization-usage/operations' +import type { ActivityDimension, ActivitySort } from '@/lib/billing/core/organization-activity' +import { + readActivityBreakdown, + readActivitySummary, + readActivityWorkspace, +} from '@/lib/billing/core/organization-activity-queries' +import { + resolveUsageAnalyticsWindow, + resolveUsageBucket, + usageBucketTimestamps, + usageWindowBounds, +} from '@/lib/billing/core/usage-analytics' +import { OrchestrationError } from '@/lib/core/orchestration/types' + +interface OrganizationActivityBreakdownInput extends OrganizationUsageSummaryInput { + dimension: ActivityDimension + sort: ActivitySort + page: number +} + +async function resolveActivityScope( + input: OrganizationUsageSummaryInput, + context: AuthorizedOrganizationUsageContext +) { + const workspace = input.workspaceId + ? await readActivityWorkspace(context.organizationId, input.workspaceId) + : null + if (input.workspaceId && !workspace) { + throw new OrchestrationError('not_found', 'Workspace not found') + } + const window = resolveUsageAnalyticsWindow({ + preset: input.preset, + period: context.period, + customStart: input.startDate, + customEnd: input.endDate, + timezone: input.timezone, + }) + return { + window, + workspace, + scope: { + organizationId: context.organizationId, + workspaceId: workspace?.id, + ...usageWindowBounds(window), + }, + } +} + +export const getOrganizationActivitySummary = defineAuthorizedOrganizationUsageUseCase({ + operation: organizationUsageOperations.readActivitySummary, + organizationId: (input: OrganizationUsageSummaryInput) => input.organizationId, + async execute({ input, context }) { + const { window, workspace, scope } = await resolveActivityScope(input, context) + const bucket = resolveUsageBucket(window) + const result = await readActivitySummary(scope, bucket, input.timezone) + const byTimestamp = new Map(result.series.map((point) => [point.timestamp, point])) + return { + workspace, + totals: result.totals, + series: usageBucketTimestamps(window, bucket, input.timezone).map( + (timestamp) => + byTimestamp.get(timestamp) ?? { timestamp, workflowRuns: 0, chatRuns: 0, failed: 0 } + ), + } + }, +}) + +export const getOrganizationActivityBreakdown = defineAuthorizedOrganizationUsageUseCase({ + operation: organizationUsageOperations.readActivityBreakdown, + organizationId: (input: OrganizationActivityBreakdownInput) => input.organizationId, + async execute({ input, context }) { + const { scope } = await resolveActivityScope(input, context) + return readActivityBreakdown(scope, input.dimension, input.sort, input.page) + }, +}) diff --git a/apps/sim/lib/billing/application/organization-usage/operations.ts b/apps/sim/lib/billing/application/organization-usage/operations.ts index 75a234a5358..8038552f325 100644 --- a/apps/sim/lib/billing/application/organization-usage/operations.ts +++ b/apps/sim/lib/billing/application/organization-usage/operations.ts @@ -48,6 +48,18 @@ const BASE = { * nothing outside the type system ever sees. */ export const organizationUsageOperations = { + // permission-group-exempt: aggregate organization activity is governed by organization admin authority, not a workspace permission group + readActivitySummary: defineOrganizationUsageOperation({ + id: 'organization_usage.activity.summary.read', + capability: 'none', + ...BASE, + }), + // permission-group-exempt: organization activity breakdowns require the same organization admin authority as the summary + readActivityBreakdown: defineOrganizationUsageOperation({ + id: 'organization_usage.activity.breakdown.read', + capability: 'none', + ...BASE, + }), // permission-group-exempt: the organization's pooled ledger is authorized by organization billing-admin authority, which no workspace-shaped group key names readSummary: defineOrganizationUsageOperation({ id: 'organization_usage.summary.read', diff --git a/apps/sim/lib/billing/core/organization-activity-queries.ts b/apps/sim/lib/billing/core/organization-activity-queries.ts new file mode 100644 index 00000000000..9aaab0adfbd --- /dev/null +++ b/apps/sim/lib/billing/core/organization-activity-queries.ts @@ -0,0 +1,179 @@ +import { dbReplica } from '@sim/db' +import { + copilotChats, + copilotRuns, + user, + workflow, + workflowExecutionLogs, + workspace, +} from '@sim/db/schema' +import { and, eq, sql } from 'drizzle-orm' +import { + ACTIVITY_PAGE_SIZE, + type ActivityAggregate, + type ActivityDimension, + type ActivityScope, + type ActivitySort, + activityMetrics, +} from '@/lib/billing/core/organization-activity' +import type { UsageBucket } from '@/lib/billing/core/usage-analytics' + +export async function readActivityWorkspace(organizationId: string, workspaceId: string) { + const [row] = await dbReplica + .select({ id: workspace.id, name: workspace.name }) + .from(workspace) + .where(and(eq(workspace.id, workspaceId), eq(workspace.organizationId, organizationId))) + .limit(1) + return row ?? null +} + +/** + * Scope by the owning workspace or organization chat, never the user's memberships. + * Only lightweight execution columns are read; transcripts and trace payloads stay private. + * Chat continuations share an execution id and belong to their first retained start. + */ +function activityCte(scope: ActivityScope, dimension?: ActivityDimension) { + const start = sql`(${scope.start.toISOString()}::timestamptz AT TIME ZONE 'UTC')` + const end = sql`(${scope.end.toISOString()}::timestamptz AT TIME ZONE 'UTC')` + const workflows = sql` + SELECT 'workflow' AS kind, l.workspace_id, l.workflow_id, NULL::text AS member_id, + l.trigger, l.started_at, l.status, + CASE WHEN l.status IN ('completed', 'failed') AND l.total_duration_ms >= 0 + THEN l.total_duration_ms END AS duration_ms + FROM ${workflowExecutionLogs} l + JOIN ${workspace} w ON w.id = l.workspace_id + WHERE w.organization_id = ${scope.organizationId} + AND l.started_at >= ${start} AND l.started_at < ${end} + ${scope.workspaceId ? sql`AND l.workspace_id = ${scope.workspaceId}` : sql``} + ` + /** Separate ownership branches let Postgres use the workspace and organization chat indexes. */ + const workspaceChats = sql` + SELECT c.id, c.workspace_id FROM ${copilotChats} c + JOIN ${workspace} w ON w.id = c.workspace_id + WHERE w.organization_id = ${scope.organizationId} + ${scope.workspaceId ? sql`AND c.workspace_id = ${scope.workspaceId}` : sql``} + ` + const scopedChats = scope.workspaceId + ? workspaceChats + : sql`${workspaceChats} UNION ALL + SELECT c.id, c.workspace_id FROM ${copilotChats} c + WHERE c.organization_id = ${scope.organizationId}` + const chats = sql` + SELECT DISTINCT ON (r.execution_id) + 'chat' AS kind, c.workspace_id, NULL::text AS workflow_id, r.user_id AS member_id, + NULL::text AS trigger, r.started_at, NULL::text AS status, NULL::integer AS duration_ms + FROM ${copilotRuns} r + JOIN (${scopedChats}) c ON c.id = r.chat_id + WHERE r.started_at >= ${start} AND r.started_at < ${end} + AND NOT EXISTS ( + SELECT 1 FROM ${copilotRuns} earlier + WHERE earlier.execution_id = r.execution_id AND earlier.started_at < ${start} + ) + ORDER BY r.execution_id, r.started_at, r.id + ` + const source = + dimension === 'member' + ? chats + : dimension === 'workflow' || dimension === 'trigger' + ? workflows + : sql`(${workflows}) UNION ALL (${chats})` + return sql`WITH activity AS (${source})` +} + +const aggregates = sql` + count(*) FILTER (WHERE a.kind = 'workflow') AS "workflowRuns", + count(*) FILTER (WHERE a.kind = 'workflow' AND a.status = 'completed') AS completed, + count(*) FILTER (WHERE a.kind = 'workflow' AND a.status = 'failed') AS failed, + count(*) FILTER (WHERE a.kind = 'chat') AS "chatRuns", + count(DISTINCT a.member_id) AS "chatMembers", + avg(a.duration_ms) AS "averageDurationMs" +` + +export async function readActivitySummary( + scope: ActivityScope, + bucket: UsageBucket, + timezone: string +) { + const rows = await dbReplica.execute(sql` + ${activityCte(scope)} + SELECT to_char(date_trunc(${bucket}, (started_at AT TIME ZONE 'UTC') AT TIME ZONE ${timezone}), + 'YYYY-MM-DD') AS bucket, ${aggregates} + FROM activity a GROUP BY GROUPING SETS ((1), ()) + `) + return { + totals: activityMetrics(rows.find((row) => row.bucket === null)), + series: rows.flatMap((row) => + row.bucket === null + ? [] + : [ + { + timestamp: `${row.bucket}T00:00:00`, + workflowRuns: Number(row.workflowRuns), + chatRuns: Number(row.chatRuns), + failed: Number(row.failed), + }, + ] + ), + } +} + +/** Aggregation and pagination happen in Postgres; no run history is materialized in the app. */ +export async function readActivityBreakdown( + scope: ActivityScope, + dimension: ActivityDimension, + sort: ActivitySort, + page: number +) { + const id = { + workspace: sql`coalesce(a.workspace_id, 'organization')`, + workflow: sql`coalesce(a.workflow_id, 'deleted:' || a.workspace_id)`, + member: sql`a.member_id`, + trigger: sql`a.trigger`, + }[dimension] + const label = { + workspace: sql`coalesce(w.name, 'Organization chats')`, + workflow: sql`coalesce(f.name, 'Deleted workflows')`, + member: sql`coalesce(u.name, 'Deleted member')`, + trigger: sql`a.id`, + }[dimension] + const hasWorkspace = dimension === 'workspace' || dimension === 'workflow' + const workspaceId = hasWorkspace ? sql`a.workspace_id` : sql`NULL::text` + const workspaceName = hasWorkspace ? sql`w.name` : sql`NULL::text` + const order = { + runs: sql`("workflowRuns" + "chatRuns") DESC`, + failures: sql`failed DESC`, + duration: sql`"averageDurationMs" DESC NULLS LAST`, + }[sort] + const rows = await dbReplica.execute< + ActivityAggregate & { + id: string + label: string + workspaceId: string | null + workspaceName: string | null + } + >(sql` + ${activityCte(scope, dimension)}, grouped AS ( + SELECT ${id} AS id, ${workspaceId} AS "workspaceId", ${aggregates} + FROM activity a + GROUP BY 1, 2 + ), named AS ( + SELECT a.*, ${label} AS label, ${workspaceName} AS "workspaceName" + FROM grouped a + ${hasWorkspace ? sql`LEFT JOIN ${workspace} w ON w.id = a."workspaceId"` : sql``} + ${dimension === 'workflow' ? sql`LEFT JOIN ${workflow} f ON f.id = a.id` : sql``} + ${dimension === 'member' ? sql`LEFT JOIN ${user} u ON u.id = a.id` : sql``} + ) + SELECT * FROM named ORDER BY ${order}, label, id + LIMIT ${ACTIVITY_PAGE_SIZE + 1} OFFSET ${page * ACTIVITY_PAGE_SIZE} + `) + return { + rows: rows.slice(0, ACTIVITY_PAGE_SIZE).map((row) => ({ + id: row.id, + label: row.label, + workspaceId: row.workspaceId, + workspaceName: row.workspaceName, + ...activityMetrics(row), + })), + hasMore: rows.length > ACTIVITY_PAGE_SIZE, + } +} diff --git a/apps/sim/lib/billing/core/organization-activity.postgres.test.ts b/apps/sim/lib/billing/core/organization-activity.postgres.test.ts new file mode 100644 index 00000000000..852317aa42c --- /dev/null +++ b/apps/sim/lib/billing/core/organization-activity.postgres.test.ts @@ -0,0 +1,187 @@ +/** @vitest-environment node */ +import { generateId } from '@sim/utils/id' +import { drizzle } from 'drizzle-orm/postgres-js' +import postgres from 'postgres' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' + +const { databaseUrl, execute, select } = vi.hoisted(() => { + const databaseUrl = process.env.BILLING_USAGE_TEST_DATABASE_URL + if (databaseUrl && !['localhost', '127.0.0.1', '[::1]'].includes(new URL(databaseUrl).hostname)) { + throw new Error('Activity integration tests require a disposable local database') + } + return { databaseUrl, execute: vi.fn(), select: vi.fn() } +}) + +vi.unmock('drizzle-orm') +vi.unmock('@sim/db/schema') +vi.mock('@sim/db', () => ({ dbReplica: { execute, select } })) + +import { + readActivityBreakdown, + readActivitySummary, + readActivityWorkspace, +} from '@/lib/billing/core/organization-activity-queries' +import { + resolveUsageAnalyticsWindow, + usageBucketTimestamps, +} from '@/lib/billing/core/usage-analytics' + +const schemaName = `activity_${generateId().replaceAll('-', '')}` +const connection = databaseUrl + ? postgres(databaseUrl, { + max: 1, + prepare: false, + fetch_types: false, + connection: { search_path: schemaName, timezone: 'Pacific/Auckland' }, + onnotice: () => undefined, + }) + : undefined +const database = connection ? drizzle(connection) : undefined +const scope = { + organizationId: 'org', + start: new Date('2026-03-08T08:00:00Z'), + end: new Date('2026-03-10T07:00:00Z'), +} + +beforeAll(async () => { + if (!connection || !database) return + await connection.unsafe(`CREATE SCHEMA "${schemaName}"`) + await connection.unsafe(` + CREATE TABLE workspace (id text PRIMARY KEY, name text, organization_id text); + CREATE TABLE workflow (id text PRIMARY KEY, name text); + CREATE TABLE "user" (id text PRIMARY KEY, name text); + CREATE TABLE workflow_execution_logs (id text PRIMARY KEY, workspace_id text, workflow_id text, + trigger text, started_at timestamp, status text, total_duration_ms integer); + CREATE TABLE copilot_chats (id text PRIMARY KEY, workspace_id text, organization_id text); + CREATE TABLE copilot_runs (id text PRIMARY KEY, chat_id text, execution_id text, user_id text, started_at timestamp); + INSERT INTO workspace VALUES ('w1', 'Support', 'org'), ('w2', 'Sales', 'org'), ('foreign', 'Private', 'other'); + INSERT INTO workflow VALUES ('f1', 'Triage'), ('f2', 'Follow up'); + INSERT INTO "user" VALUES ('m1', 'Alex'), ('m2', 'Sam'); + INSERT INTO workflow_execution_logs VALUES + ('l1', 'w1', 'f1', 'manual', '2026-03-08 08:00:00', 'completed', 1000), + ('l2', 'w1', 'f1', 'api', '2026-03-09 06:59:59', 'failed', 3000), + ('l3', 'w1', 'f1', 'schedule', '2026-03-09 07:00:00', 'paused', 999999), + ('l4', 'w2', 'f2', 'api', '2026-03-09 10:00:00', 'cancelled', 999999), + ('l5', 'w2', NULL, 'webhook', '2026-03-09 11:00:00', 'running', NULL), + ('before', 'w1', 'f1', 'manual', '2026-03-08 07:59:59', 'failed', 999999), + ('end', 'w1', 'f1', 'manual', '2026-03-10 07:00:00', 'failed', 999999), + ('private', 'foreign', 'f1', 'manual', '2026-03-09 10:00:00', 'failed', 999999); + INSERT INTO copilot_chats VALUES ('c1', 'w1', NULL), ('c2', NULL, 'org'), + ('c3', 'foreign', NULL), ('personal', NULL, NULL); + INSERT INTO copilot_runs VALUES + ('r1', 'c1', 'e1', 'm1', '2026-03-08 08:00:00'), + ('r2', 'c1', 'e1', 'm1', '2026-03-09 10:00:00'), + ('r3', 'c2', 'e2', 'm1', '2026-03-09 10:00:00'), + ('r4', 'c2', 'e3', 'm2', '2026-03-09 10:00:00'), + ('r5', 'c1', 'old', 'm2', '2026-03-01 08:00:00'), + ('r6', 'c1', 'old', 'm2', '2026-03-09 10:00:00'), + ('r7', 'c3', 'foreign', 'm1', '2026-03-09 10:00:00'), + ('r8', 'personal', 'personal', 'm1', '2026-03-09 10:00:00'); + `) + execute.mockImplementation((query) => database.execute(query)) + select.mockImplementation((fields) => database.select(fields)) +}) + +afterAll(async () => { + if (!connection) return + await connection.unsafe(`DROP SCHEMA IF EXISTS "${schemaName}" CASCADE`) + await connection.end() +}) + +describe.skipIf(!databaseUrl)('organization activity SQL', () => { + it('isolates tenants and personal chats, deduplicates continuations, and excludes unfinished durations', async () => { + const result = await readActivitySummary(scope, 'day', 'America/Los_Angeles') + expect(result.totals).toEqual({ + workflowRuns: 5, + completed: 1, + failed: 1, + chatRuns: 3, + chatMembers: 2, + failureRate: 0.5, + averageDurationMs: 2000, + }) + expect(result.series.toSorted((a, b) => a.timestamp.localeCompare(b.timestamp))).toEqual([ + { timestamp: '2026-03-08T00:00:00', workflowRuns: 2, chatRuns: 1, failed: 1 }, + { timestamp: '2026-03-09T00:00:00', workflowRuns: 3, chatRuns: 2, failed: 0 }, + ]) + }) + + it('uses a half-open local calendar window across daylight saving time', () => { + const window = resolveUsageAnalyticsWindow({ + preset: 'custom', + customStart: new Date('2026-03-08'), + customEnd: new Date('2026-03-09'), + timezone: 'America/Los_Angeles', + period: { start: scope.start, end: scope.end, source: 'stripe' }, + }) + expect(window).toEqual({ kind: 'range', from: scope.start, to: scope.end }) + expect(usageBucketTimestamps(window, 'day', 'America/Los_Angeles')).toEqual([ + '2026-03-08T00:00:00', + '2026-03-09T00:00:00', + ]) + }) + + it('scopes workspace summaries and rejects foreign workspace lookups', async () => { + const result = await readActivitySummary({ ...scope, workspaceId: 'w1' }, 'day', 'UTC') + expect(result.totals).toMatchObject({ workflowRuns: 3, chatRuns: 1, chatMembers: 1 }) + expect(await readActivityWorkspace('org', 'foreign')).toBeNull() + expect(await readActivityWorkspace('org', 'w1')).toEqual({ id: 'w1', name: 'Support' }) + }) + + it('keeps missing terminal outcomes distinct from zero failure and ranks the complete population', async () => { + const result = await readActivityBreakdown(scope, 'workspace', 'failures', 0) + expect(result.rows[0]).toMatchObject({ id: 'w1', failed: 1, failureRate: 0.5 }) + expect(result.rows.find((row) => row.id === 'w2')).toMatchObject({ + failureRate: null, + averageDurationMs: null, + }) + expect(result.rows.find((row) => row.id === 'organization')).toMatchObject({ chatRuns: 2 }) + expect(result.rows.reduce((sum, row) => sum + row.workflowRuns, 0)).toBe(5) + expect(result.rows.reduce((sum, row) => sum + row.chatRuns, 0)).toBe(3) + }) + + it('supports all grouping dimensions without attributing workflows to billed members', async () => { + const members = await readActivityBreakdown(scope, 'member', 'runs', 0) + expect(members.rows.map((row) => [row.id, row.chatRuns, row.workflowRuns])).toEqual([ + ['m1', 2, 0], + ['m2', 1, 0], + ]) + const workflows = await readActivityBreakdown(scope, 'workflow', 'duration', 0) + expect(workflows.rows[0]).toMatchObject({ id: 'f1', averageDurationMs: 2000, workflowRuns: 3 }) + expect(workflows.rows.find((row) => row.id === 'deleted:w2')).toMatchObject({ + label: 'Deleted workflows', + }) + const triggers = await readActivityBreakdown(scope, 'trigger', 'runs', 0) + expect(triggers.rows[0]).toMatchObject({ id: 'api', workflowRuns: 2 }) + }) + + it('paginates aggregated rows deterministically without losing tied rows', async () => { + if (!connection) throw new Error('Missing fixture') + await connection`INSERT INTO workflow_execution_logs + SELECT 'page-' || i, 'w1', 'f1', 'trigger-' || lpad(i::text, 2, '0'), + '2026-04-01'::timestamp, 'completed', 0 FROM generate_series(1, 27) i` + const pageScope = { ...scope, start: new Date('2026-04-01'), end: new Date('2026-04-02') } + const first = await readActivityBreakdown(pageScope, 'trigger', 'runs', 0) + const second = await readActivityBreakdown(pageScope, 'trigger', 'runs', 1) + expect(first.rows).toHaveLength(25) + expect(first.hasMore).toBe(true) + expect(second.rows).toHaveLength(2) + expect(second.hasMore).toBe(false) + expect(new Set([...first.rows, ...second.rows].map((row) => row.id)).size).toBe(27) + expect(first.rows[0]).toMatchObject({ averageDurationMs: 0, failureRate: 0 }) + }) + + it('returns true zeros and null rates for an empty organization', async () => { + expect( + (await readActivitySummary({ ...scope, organizationId: 'empty' }, 'day', 'UTC')).totals + ).toEqual({ + workflowRuns: 0, + completed: 0, + failed: 0, + chatRuns: 0, + chatMembers: 0, + failureRate: null, + averageDurationMs: null, + }) + }) +}) diff --git a/apps/sim/lib/billing/core/organization-activity.ts b/apps/sim/lib/billing/core/organization-activity.ts new file mode 100644 index 00000000000..c93a10dee57 --- /dev/null +++ b/apps/sim/lib/billing/core/organization-activity.ts @@ -0,0 +1,48 @@ +export const ACTIVITY_DIMENSIONS = ['workspace', 'workflow', 'member', 'trigger'] as const +export type ActivityDimension = (typeof ACTIVITY_DIMENSIONS)[number] + +export const ACTIVITY_SORTS = ['runs', 'failures', 'duration'] as const +export type ActivitySort = (typeof ACTIVITY_SORTS)[number] +export const ACTIVITY_PAGE_SIZE = 25 +export const ACTIVITY_MAX_PAGE = 1000 + +export interface ActivityMetrics { + workflowRuns: number + completed: number + failed: number + chatRuns: number + chatMembers: number + failureRate: number | null + averageDurationMs: number | null +} + +export interface ActivityScope { + organizationId: string + workspaceId?: string + start: Date + end: Date +} + +export type ActivityAggregate = { + workflowRuns: string | number + completed: string | number + failed: string | number + chatRuns: string | number + chatMembers: string | number + averageDurationMs: string | number | null +} + +/** Terminal failures exclude cancelled, paused, and still-running executions. */ +export function activityMetrics(row?: ActivityAggregate): ActivityMetrics { + const completed = Number(row?.completed ?? 0) + const failed = Number(row?.failed ?? 0) + return { + workflowRuns: Number(row?.workflowRuns ?? 0), + completed, + failed, + chatRuns: Number(row?.chatRuns ?? 0), + chatMembers: Number(row?.chatMembers ?? 0), + failureRate: completed + failed > 0 ? failed / (completed + failed) : null, + averageDurationMs: row?.averageDurationMs == null ? null : Number(row.averageDurationMs), + } +} diff --git a/apps/sim/lib/billing/core/usage-analytics.ts b/apps/sim/lib/billing/core/usage-analytics.ts index d50a71dbafa..736a01bdedc 100644 --- a/apps/sim/lib/billing/core/usage-analytics.ts +++ b/apps/sim/lib/billing/core/usage-analytics.ts @@ -407,12 +407,28 @@ export function densifyUsageSeries( if (row.bucketStart) byBucket.set(row.bucketStart.slice(0, 10), row) } + return usageBucketTimestamps(window, bucket, timezone).map((timestamp) => { + const row = byBucket.get(timestamp.slice(0, 10)) + return { + timestamp, + cost: toNumber(row?.cost), + events: Math.round(toNumber(row?.events)), + } + }) +} + +/** Calendar-aligned buckets shared by credit and activity series, including empty days. */ +export function usageBucketTimestamps( + window: UsageAnalyticsWindow, + bucket: UsageBucket, + timezone: string +): string[] { const { start, end } = usageWindowBounds(window) const first = truncateToBucket(localCalendarDate(start, timezone), bucket) // The window is half-open, so the last bucket is the one holding its final instant. const last = truncateToBucket(localCalendarDate(new Date(end.getTime() - 1), timezone), bucket) - const points: UsageSeriesPoint[] = [] + const points: string[] = [] const cursor = civilDate(first) let guard = 0 @@ -420,12 +436,7 @@ export function densifyUsageSeries( while (civilKey(cursor) <= last && guard < 1000) { guard += 1 const key = civilKey(cursor) - const row = byBucket.get(key) - points.push({ - timestamp: `${key}T00:00:00`, - cost: toNumber(row?.cost), - events: Math.round(toNumber(row?.events)), - }) + points.push(`${key}T00:00:00`) if (bucket === 'day') cursor.setUTCDate(cursor.getUTCDate() + 1) else if (bucket === 'week') cursor.setUTCDate(cursor.getUTCDate() + 7) else cursor.setUTCMonth(cursor.getUTCMonth() + 1) diff --git a/packages/db/migrations/0351_copilot_run_activity_index.sql b/packages/db/migrations/0351_copilot_run_activity_index.sql new file mode 100644 index 00000000000..fc08f566dcf --- /dev/null +++ b/packages/db/migrations/0351_copilot_run_activity_index.sql @@ -0,0 +1,7 @@ +COMMIT;--> statement-breakpoint +SET lock_timeout = 0;--> statement-breakpoint +-- migration-safe: replay removes only this new index to recover an interrupted concurrent build; existing indexes remain available. +DROP INDEX CONCURRENTLY IF EXISTS "copilot_runs_chat_started_at_idx";--> statement-breakpoint +CREATE INDEX CONCURRENTLY IF NOT EXISTS "copilot_runs_chat_started_at_idx" ON "copilot_runs" USING btree ("chat_id","started_at"); +--> statement-breakpoint +SET lock_timeout = '5s'; diff --git a/packages/db/migrations/meta/0351_snapshot.json b/packages/db/migrations/meta/0351_snapshot.json new file mode 100644 index 00000000000..8e03198124b --- /dev/null +++ b/packages/db/migrations/meta/0351_snapshot.json @@ -0,0 +1,27051 @@ +{ + "id": "68c2e94c-a38f-4cb3-aded-181cd867260f", + "prevId": "ddf5e64a-eadd-4249-b0f4-ff84d8c0e652", + "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.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 + }, + "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_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_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" + } + }, + "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.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_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_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_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_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": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "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_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_vector_hnsw_idx": { + "name": "embedding_384_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_vector_hnsw_idx": { + "name": "embedding_768_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_vector_hnsw_idx": { + "name": "embedding_1024_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_vector_hnsw_idx": { + "name": "embedding_3072_vector_hnsw_idx", + "columns": [ + { + "expression": "(\"embedding_3072\"::halfvec(3072)) halfvec_cosine_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_binary_hnsw_idx": { + "name": "embedding_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding\")::bit(1536)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_binary_hnsw_idx": { + "name": "embedding_384_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_384\")::bit(384)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_binary_hnsw_idx": { + "name": "embedding_768_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_768\")::bit(768)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_binary_hnsw_idx": { + "name": "embedding_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_1024\")::bit(1024)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_binary_hnsw_idx": { + "name": "embedding_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_3072\")::bit(3072)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "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": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_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_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 + }, + "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_binary_hnsw_idx": { + "name": "embedding_search_binary_hnsw_idx", + "columns": [ + { + "expression": "binary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_384_binary_hnsw_idx": { + "name": "embedding_search_384_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_768_binary_hnsw_idx": { + "name": "embedding_search_768_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_1024_binary_hnsw_idx": { + "name": "embedding_search_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_3072_binary_hnsw_idx": { + "name": "embedding_search_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "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", + "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 + }, + "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", + "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 + } + }, + "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 + }, + "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_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_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 + } + }, + "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')" + } + }, + "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 + } + }, + "indexes": { + "kcsl_connector_started_at_idx": { + "name": "kcsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcsl_started_at_partial_idx": { + "name": "kcsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector_sync_log\".\"status\" = 'started'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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.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 + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_secret_provenance": { + "name": "memory_secret_provenance", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memory_secret_provenance_memory_id_memory_id_fk": { + "name": "memory_secret_provenance_memory_id_memory_id_fk", + "tableFrom": "memory_secret_provenance", + "tableTo": "memory", + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_secret_provenance_status_check": { + "name": "memory_secret_provenance_status_check", + "value": "\"memory_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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_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.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.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_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_period_cost_idx": { + "name": "usage_log_billing_period_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_created_at_cost_idx": { + "name": "usage_log_billing_entity_created_at_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_deadline_idx": { + "name": "workflow_execution_logs_running_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_started_at_idx": { + "name": "workflow_execution_logs_redacting_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'redacting'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_deadline_idx": { + "name": "workflow_execution_logs_redacting_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "mounted_secrets": { + "name": "mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_operation", + "columnsFrom": ["deployment_operation_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_secret_scope": { + "name": "inbox_secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "inbox_mounted_secrets": { + "name": "inbox_mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_inbox_provider_id_idx": { + "name": "workspace_inbox_provider_id_idx", + "columns": [ + { + "expression": "inbox_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_storage_used_bytes_non_negative": { + "name": "workspace_storage_used_bytes_non_negative", + "value": "\"workspace\".\"storage_used_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_collab_state": { + "name": "workspace_file_collab_state", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "doc_state": { + "name": "doc_state", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_collab_state_file_id_workspace_files_id_fk": { + "name": "workspace_file_collab_state_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_collab_state", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_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_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_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_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": "now()" + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_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 + } + }, + "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_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 57d549368ea..2444c425a11 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2451,6 +2451,13 @@ "when": 1789579589946, "tag": "0350_organization_require_sso", "breakpoints": true + }, + { + "idx": 351, + "version": "7", + "when": 1789583796385, + "tag": "0351_copilot_run_activity_index", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 18c68688d1b..833473fff8b 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -3726,6 +3726,7 @@ export const copilotRuns = pgTable( executionIdIdx: index('copilot_runs_execution_id_idx').on(table.executionId), parentRunIdIdx: index('copilot_runs_parent_run_id_idx').on(table.parentRunId), chatIdIdx: index('copilot_runs_chat_id_idx').on(table.chatId), + chatStartedAtIdx: index('copilot_runs_chat_started_at_idx').on(table.chatId, table.startedAt), userIdIdx: index('copilot_runs_user_id_idx').on(table.userId), workflowIdIdx: index('copilot_runs_workflow_id_idx').on(table.workflowId), workspaceIdIdx: index('copilot_runs_workspace_id_idx').on(table.workspaceId), diff --git a/apps/sim/components/charts/bar-chart.tsx b/packages/emcn/src/components/charts/bar-chart.tsx similarity index 68% rename from apps/sim/components/charts/bar-chart.tsx rename to packages/emcn/src/components/charts/bar-chart.tsx index 2e04c6e12e7..1f7ff4ccd43 100644 --- a/apps/sim/components/charts/bar-chart.tsx +++ b/packages/emcn/src/components/charts/bar-chart.tsx @@ -1,36 +1,30 @@ 'use client' import { memo, useId, useMemo, useState } from 'react' -import { cn } from '@sim/emcn' -import { - formatChartCompactNumber, - formatChartLatency, - formatChartTimestamp, -} from '@/components/charts/chart-format' import { CHART_AXIS_LABEL_GAP, CHART_DEFAULT_HEIGHT, CHART_GRID_FRACTIONS, CHART_TICK_FILL, CHART_TICK_FONT_SIZE, - chartPlotBand, - formatTimeTick, - resolveChartPadding, - resolveSpanMs, - resolveTimeTickIndices, -} from '@/components/charts/chart-geometry' -import { + ChartDataTable, ChartTooltip, ChartTooltipRow, + chartPlotBand, + cn, estimateTooltipHeight, estimateTooltipWidth, + formatChartCompactNumber, + formatChartLatency, + formatChartTimestamp, + formatTimeTick, positionChartTooltip, -} from '@/components/charts/chart-tooltip' -import { + resolveChartPadding, + resolveSpanMs, + resolveTimeTickIndices, useChartWidth, useIsDarkTheme, - useResolvedChartColors, -} from '@/components/charts/use-chart-theme' +} from '@sim/emcn' export interface BarChartPoint { timestamp: string @@ -47,6 +41,8 @@ interface BarChartProps { height?: number /** Display bucket dates in this zone; omitted uses the viewer’s local zone. */ timeZone?: string + /** Calendar buckets retain date labels even for a single day. */ + xAxisFormat?: 'auto' | 'date' /** Bucket drawn at full opacity, e.g. the period in progress. */ highlightIndex?: number } @@ -59,17 +55,10 @@ function formatBarValue(value: number | undefined, unit: string | undefined): st if (suffix === 'latency') return formatChartLatency(value) if (suffix.includes('ms')) return `${Math.round(value)}ms` if (suffix === 'credits') return formatChartCompactNumber(value) - return `${Math.round(value)}${unit ?? ''}` + return `${Math.round(value).toLocaleString()}${unit ?? ''}` } -/** - * Discrete time buckets as bars. - * - * The sibling of {@link LineChart}, and deliberately built from the same geometry, - * tooltip, and theme modules: a smoothed line implies a continuous signal between - * samples, which is wrong for a calendar bucket like a day's spend, but the two must - * still line up pixel-for-pixel when stacked in one card. - */ +/** Discrete time buckets using the shared chart geometry and tooltip. */ function BarChartComponent({ data, label, @@ -78,12 +67,8 @@ function BarChartComponent({ height = CHART_DEFAULT_HEIGHT, highlightIndex, timeZone, + xAxisFormat = 'auto', }: BarChartProps) { - /* - `useId`, not `useRef(generateShortId())`: a ref initializer is evaluated on - every render and all but the first result thrown away, and React already has - a hook whose whole job is a stable unique id. - */ const uniqueId = useId().replace(/:/g, '') const [containerRef, containerWidth] = useChartWidth() const width = containerWidth ?? 0 @@ -91,39 +76,26 @@ function BarChartComponent({ const isDark = useIsDarkTheme() const [hoverPos, setHoverPos] = useState<{ x: number; y: number } | null>(null) - const resolvedColors = useResolvedChartColors({ base: color }) - const resolvedColor = resolvedColors.base || color - const hasExternalWrapper = !label - /** - * The track is read against its own background, so its opacity is per-theme - * rather than one shared value. `--border` is the platform's neutral track - * token — the same one the proportional row meters use — but it resolves to - * `#444` on dark and `#d8d8d8` on light, and a strength that reads as a column - * on near-black is a half-percent delta on white. Hover keeps the same ratio. - */ + /** Tracks need stronger opacity on light backgrounds to remain visible. */ const trackOpacity = isDark ? 0.12 : 0.3 const trackHoverOpacity = isDark ? 0.22 : 0.5 const maxValue = useMemo(() => { const peak = Math.max(...data.map((d) => d.value), 0) - return peak <= 0 ? 1 : peak * 1.1 - }, [data]) + return peak <= 0 ? 1 : unit ? peak * 1.1 : Math.ceil(peak * 1.1) + }, [data, unit]) - const padding = resolveChartPadding([formatBarValue(maxValue, unit), '0']) + const maximumLabel = unit ? formatBarValue(maxValue, unit) : formatChartCompactNumber(maxValue) + const padding = resolveChartPadding([maximumLabel, '0']) const chartWidth = width - padding.left - padding.right const chartHeight = height - padding.top - padding.bottom - /** Slot geometry: every bucket owns an equal slice, with the bar centred in it. */ const slot = data.length > 0 ? Math.max(1, chartWidth) / data.length : 0 const barWidth = Math.max(1, Math.min(24, slot * 0.7)) - /** - * Bars own a slot, so the hovered bucket is which slot the cursor is in — not the - * nearest sample, which is how a line chart resolves it. Derived, so a resize - * mid-hover cannot leave an index disagreeing with the slot geometry. - */ + /** Derive the hovered slot after resizing so the index stays within the current geometry. */ const hoverIndex = hoverPos === null || data.length === 0 || slot <= 0 ? null @@ -138,13 +110,7 @@ function BarChartComponent({ return { x, y, - /* - * A zero bucket draws nothing. The clamp above keeps a *drawn* bar off the - * axis rule, but applied to zero it floored the bar at the 3px band and - * every empty day rendered as a small amount of usage — the densified zeros - * this chart exists to show honestly. Only the track represents an empty - * bucket. - */ + /** Empty buckets must stay at zero despite the plot-band clamp. */ height: point.value > 0 ? Math.max(0, height - padding.bottom - y) : 0, point, } @@ -167,21 +133,15 @@ function BarChartComponent({ if (data.length === 0) { return ( - // Keeps the measurement ref: dropping it here left the observer watching a - // detached node, so a resize while empty was never seen and the next non-empty - // render laid out at the stale width. + /** Keeps the measurement ref: dropping it here left the observer watching a */ + /** detached node, so a resize while empty was never seen and the next non-empty */ + /** render laid out at the stale width. */

No data

@@ -196,13 +156,7 @@ function BarChartComponent({
)}
+ + + + + + {series.map((item) => ( + + ))} + + + + {timestamps.map((timestamp) => ( + + + {series.map((item, index) => ( + + ))} + + ))} + +
{label}
Date + {item.label} +
{formatChartTimestamp(timestamp, timeZone)} + {values[index] + .get(timestamp) + ?.toLocaleString(undefined, { maximumSignificantDigits: 21 }) ?? '—'} +
+ ) +} diff --git a/apps/sim/components/charts/chart-format.ts b/packages/emcn/src/components/charts/chart-format.ts similarity index 73% rename from apps/sim/components/charts/chart-format.ts rename to packages/emcn/src/components/charts/chart-format.ts index fe31db771d2..fec8ac50f30 100644 --- a/apps/sim/components/charts/chart-format.ts +++ b/packages/emcn/src/components/charts/chart-format.ts @@ -1,13 +1,4 @@ import { formatDuration } from '@sim/utils/formatting' -import { format } from 'date-fns' - -/** - * Value and tick formatting shared by the chart family. - * - * These live here rather than in the logs feature's `utils.ts` because that module - * imports the block registry, and a chart that reached for it would drag the whole - * executable registry into every consumer's bundle. - */ /** Duration for an axis tick or tooltip. `—` for a missing or non-positive value. */ export function formatChartLatency(ms: number): string { @@ -25,7 +16,9 @@ export function formatChartTimestamp(timestamp?: string, timeZone?: string): str const time = date.toLocaleTimeString('en-US', { timeZone, hour: 'numeric', minute: '2-digit' }) return `${day.toUpperCase()} ${time}` } - return `${format(date, 'MMM d').toUpperCase()} ${format(date, 'h:mm a')}` + const day = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + const time = date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }) + return `${day.toUpperCase()} ${time}` } /** Compact axis magnitude — `1.2k`, `3.4m`. */ diff --git a/packages/emcn/src/components/charts/chart-frame.tsx b/packages/emcn/src/components/charts/chart-frame.tsx new file mode 100644 index 00000000000..b5c82934f4c --- /dev/null +++ b/packages/emcn/src/components/charts/chart-frame.tsx @@ -0,0 +1,77 @@ +'use client' + +import type { ReactNode } from 'react' +import { Chip, Tooltip } from '@sim/emcn' + +interface ChartFrameProps { + title?: string + description?: string + height?: number + loading?: boolean + error?: string + onRetry?: () => void + children: ReactNode +} + +/** Keeps chart geometry stable while data loads or fails. */ +export function ChartFrame({ + title, + description, + height = 166, + loading = false, + error, + onRetry, + children, +}: ChartFrameProps) { + return ( +
+ {title && ( +
+ {description ? ( + + + + + {description} + + ) : ( +

{title}

+ )} +
+ )} +
+ +
+ {error ? ( +
+

+ {error} +

+ {onRetry && Retry} +
+ ) : loading ? ( + Loading {title ?? 'chart'} + ) : ( + children + )} +
+
+
+ ) +} diff --git a/apps/sim/components/charts/chart-geometry.test.ts b/packages/emcn/src/components/charts/chart-geometry.test.ts similarity index 89% rename from apps/sim/components/charts/chart-geometry.test.ts rename to packages/emcn/src/components/charts/chart-geometry.test.ts index 735d7f87ac0..3a0c6524c42 100644 --- a/apps/sim/components/charts/chart-geometry.test.ts +++ b/packages/emcn/src/components/charts/chart-geometry.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { describe, expect, it } from 'vitest' + import { CHART_AXIS_LABEL_GAP, CHART_PADDING, @@ -11,7 +11,8 @@ import { resolveChartPadding, resolveSpanMs, resolveTimeTickIndices, -} from '@/components/charts/chart-geometry' +} from '@sim/emcn' +import { describe, expect, it } from 'vitest' describe('resolveTimeTickIndices', () => { it('budgets roughly one tick per 64px, clamped to 3..8', () => { @@ -21,7 +22,7 @@ describe('resolveTimeTickIndices', () => { }) it('dedupes the collisions rounding produces on a short series', () => { - // 2 points across a wide chart wants 8 ticks but only has indices 0 and 1. + /** 2 points across a wide chart wants 8 ticks but only has indices 0 and 1. */ const indices = resolveTimeTickIndices(2, 4000) expect(indices).toEqual([...new Set(indices)]) expect(indices.every((index) => index >= 0 && index < 2)).toBe(true) @@ -69,8 +70,8 @@ describe('resolveSpanMs', () => { describe('chartPlotBand', () => { it('insets the band so strokes clear the axis rules', () => { - // The line and bar charts both clamp to this band, which is what keeps them - // aligned when stacked in the same card. + /** The line and bar charts both clamp to this band, which is what keeps them */ + /** aligned when stacked in the same card. */ expect(chartPlotBand(166)).toEqual({ yMin: CHART_PADDING.top + 3, yMax: CHART_PADDING.top + (166 - CHART_PADDING.top - CHART_PADDING.bottom) - 3, @@ -93,11 +94,6 @@ describe('resolveChartPadding', () => { expect(resolveChartPadding([]).left).toBeGreaterThanOrEqual(CHART_PADDING.left) }) - /** - * Three charts sit side by side on the logs dashboard. A gutter derived exactly from - * each one's own labels put their plot origins at 26, 27 and 32 — visibly ragged - * across a row that used to share one origin. - */ it('resolves labels of similar width to the same gutter', () => { const gutters = [['5'], ['1.2s'], ['12.3k'], ['0'], ['7.3k']].map( (labels) => resolveChartPadding(labels).left diff --git a/apps/sim/components/charts/chart-geometry.ts b/packages/emcn/src/components/charts/chart-geometry.ts similarity index 56% rename from apps/sim/components/charts/chart-geometry.ts rename to packages/emcn/src/components/charts/chart-geometry.ts index 9c05f7f36c9..3d6ee534d0a 100644 --- a/apps/sim/components/charts/chart-geometry.ts +++ b/packages/emcn/src/components/charts/chart-geometry.ts @@ -1,28 +1,10 @@ -/** - * Geometry shared by every chart in the family. - * - * Extracted so a sibling chart cannot drift: bar and line charts read the same - * padding, the same clamps, the same gridlines, and resolve their x ticks the same - * way, so two charts stacked in one card line up on the pixel. - * - * Pure — no React, no DOM — so a server module can read the constants. - */ - export const CHART_PADDING = { top: 16, right: 28, bottom: 26, left: 26 } as const export type ChartPadding = { top: number; right: number; bottom: number; left: number } -/** Matches the loader placeholders callers size themselves against. */ export const CHART_DEFAULT_HEIGHT = 166 -/** - * Below this the axis labels collide, so the chart scrolls rather than compresses. - * - * Consumers pair `overflow-x-auto` with `overflow-y-hidden`: a computed `overflow-x` - * other than `visible` promotes `overflow-y: visible` to `auto`, so the tooltip's - * shadow reaching the foot of the box raised a vertical scrollbar over the chart - * whenever the cursor neared the axis. - */ +/** Minimum width before axis labels collide; narrower containers scroll horizontally. */ export const CHART_MIN_WIDTH = 280 export const CHART_TICK_FILL = 'var(--text-tertiary)' @@ -35,27 +17,10 @@ const NARROW_GLYPH = /[.,:\s]/ /** Gap between a y-axis tick label's right edge and the axis rule. */ export const CHART_AXIS_LABEL_GAP = 8 -/** - * The gutter is rounded up to a multiple of this. - * - * Charts are read side by side — the logs dashboard puts three in one row — and a - * gutter derived exactly from each chart's own labels made `5`, `1.2s` and `12.3k` - * resolve to 26, 27 and 32, so three plots that used to share an origin no longer - * did. Quantizing collapses differences this small to one value while still growing - * for a genuinely wider label, and it turns the sub-pixel slack that `Math.ceil` - * alone left into several pixels. - */ +/** Quantize gutters so comparable charts keep aligned plot origins. */ const CHART_AXIS_GUTTER_STEP = 8 -/** - * Rendered width of a right-anchored y-axis tick label. - * - * SVG `` cannot be measured before layout, so the gutter that has to hold it - * is estimated from the glyphs instead. The ratios are for the UI sans at - * {@link CHART_TICK_FONT_SIZE}: digits and letters sit near 0.58em, punctuation and - * spaces near 0.3em. Deliberately generous — an over-wide gutter costs a couple of - * plot pixels, an under-wide one clips the label against the container's edge. - */ +/** Estimate SVG label width before layout, allowing extra space to prevent clipping. */ export function estimateAxisLabelWidth(text: string): number { let width = 0 for (const character of text) { @@ -64,16 +29,7 @@ export function estimateAxisLabelWidth(text: string): number { return width * CHART_TICK_FONT_SIZE } -/** - * {@link CHART_PADDING} with a left gutter wide enough for the chart's own y-axis - * labels. - * - * The fixed 26px gutter left 18px of drawable width once the label gap is taken out, - * which fits four narrow glyphs — so any tick past `7.3k` was cut off at the left edge - * of the container. Both charts resolve their gutter through this one function from - * the labels they are about to draw, so a bar and a line chart showing comparable - * magnitudes still line up when stacked in one card, and neither can clip. - */ +/** Expand the shared left gutter to accommodate the chart’s y-axis labels. */ export function resolveChartPadding(yAxisLabels: readonly string[]): ChartPadding { const widest = yAxisLabels.reduce((max, label) => Math.max(max, estimateAxisLabelWidth(label)), 0) const required = Math.max(CHART_PADDING.left, widest + CHART_AXIS_LABEL_GAP) @@ -110,7 +66,14 @@ export function resolveTimeTickIndices(pointCount: number, usableWidth: number): * Tick label whose precision follows the window: clock time within a day and a half, * calendar day within a quarter, month beyond that. */ -export function formatTimeTick(date: Date, spanMs: number, timeZone?: string): string { +export function formatTimeTick( + date: Date, + spanMs: number, + timeZone?: string, + format: 'auto' | 'date' = 'auto' +): string { + if (format === 'date') + return date.toLocaleDateString('en-US', { timeZone, month: 'short', day: 'numeric' }) if (spanMs <= 36 * 60 * 60 * 1000) { return date.toLocaleTimeString('en-US', { timeZone, diff --git a/apps/sim/components/charts/chart-layout.test.tsx b/packages/emcn/src/components/charts/chart-layout.test.tsx similarity index 55% rename from apps/sim/components/charts/chart-layout.test.tsx rename to packages/emcn/src/components/charts/chart-layout.test.tsx index b095d90426d..f4dc13d06d6 100644 --- a/apps/sim/components/charts/chart-layout.test.tsx +++ b/packages/emcn/src/components/charts/chart-layout.test.tsx @@ -2,21 +2,9 @@ * @vitest-environment jsdom */ import { act } from 'react' +import { BarChart, CHART_PADDING, ChartFrame, DonutChart, LineChart, RadarChart } from '@sim/emcn' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { BarChart } from '@/components/charts/bar-chart' -import { CHART_PADDING } from '@/components/charts/chart-geometry' -import { RadarChart } from '@/components/charts/radar-chart' - -/** - * Rendered-geometry guards for the chart family. - * - * These assert against the real SVG the components emit rather than against the - * geometry helpers in isolation: the two clipping bugs this file exists for — a - * y-axis label cut off at the container's left edge, and a radar caption painting - * over the section beside it — were both invisible to a unit test of the maths, - * because each came from a *callsite* combining correct helpers wrongly. - */ let container: HTMLDivElement let root: Root @@ -116,7 +104,7 @@ describe('BarChart rendered geometry', () => { expect(labels.length).toBe(2) for (const label of labels) { const anchorX = Number(label.getAttribute('x')) - // Right-anchored: the glyphs run leftward from the anchor. + /** Right-anchored: the glyphs run leftward from the anchor. */ expect(anchorX - textExtent(label.textContent ?? '')).toBeGreaterThanOrEqual(0) } } @@ -172,14 +160,7 @@ describe('BarChart rendered geometry', () => { describe('RadarChart rendered geometry', () => { const LONG = 'Knowledge Base Sync' - /** - * Every caption long, not just the first. - * - * The first axis sits at twelve o'clock, where a caption is centred and has the - * whole half-width to spend — the one position that cannot overflow horizontally. - * A fixture that only made that one long proved nothing about the axes that - * actually run out of room. - */ + /** Every axis needs a long caption; the centered first axis cannot expose side overflow. */ function axesOf(count: number) { return Array.from({ length: count }, (_, index) => ({ label: `${LONG} ${index}`, @@ -212,7 +193,7 @@ describe('RadarChart rendered geometry', () => { expect(left).toBeGreaterThanOrEqual(0) expect(right).toBeLessThanOrEqual(width) - // An 'auto' baseline sits the glyphs above y; 'middle' centres them on it. + /** An 'auto' baseline sits the glyphs above y; 'middle' centres them on it. */ const capHeight = 9 const top = caption.getAttribute('dominant-baseline') === 'middle' ? y - capHeight / 2 : y - capHeight @@ -258,3 +239,138 @@ describe('RadarChart rendered geometry', () => { expect(container.textContent).toContain('No data') }) }) + +describe('Dashboard chart states', () => { + it('preserves distinct cells when equally named series change order', () => { + const data = dailySeries(3, 10) + const first = { id: 'base', label: 'Runs', color: 'red', data: dailySeries(3, 20) } + const second = { id: 'second', label: 'Runs', color: 'green', data: dailySeries(3, 30) } + mountAtWidth(400, ) + const headers = [...container.querySelectorAll('thead th')] + const cells = [...container.querySelectorAll('tbody tr:first-child td')] + expect(cells.map((cell) => cell.textContent)).toEqual(['10', '20', '30']) + act(() => container.querySelector('button')?.click()) + act(() => + root.render() + ) + const updatedHeaders = [...container.querySelectorAll('thead th')] + const updatedCells = [...container.querySelectorAll('tbody tr:first-child td')] + expect(updatedHeaders[2]).toBe(headers[3]) + expect(updatedHeaders[3]).toBe(headers[2]) + expect(updatedCells[1]).toBe(cells[2]) + expect(updatedCells[2]).toBe(cells[1]) + expect(updatedCells.map((cell) => cell.textContent)).toEqual(['10', '30', '20']) + expect(container.querySelector('path[stroke="red"]')?.getAttribute('opacity')).toBe('1') + expect(container.querySelector('path[stroke="green"]')).toBeNull() + }) + + it('previews, pins, and clears a distribution without changing its proportions', () => { + const segments = [ + { label: 'Completed', value: 90, color: 'blue' }, + { label: 'Failed', value: 10, color: 'red' }, + ] + mountAtWidth(400, ) + const failed = container.querySelector( + '[aria-label="Highlight Failed: 10"]' + )! + const completedArc = container.querySelector('circle[stroke="blue"]')! + act(() => failed.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }))) + expect(completedArc.getAttribute('opacity')).toBe('0.2') + expect(completedArc.getAttribute('stroke-dasharray')).toBe('90 10') + act(() => failed.click()) + act(() => failed.dispatchEvent(new MouseEvent('mouseout', { bubbles: true }))) + expect(failed.getAttribute('aria-pressed')).toBe('true') + expect(completedArc.getAttribute('opacity')).toBe('0.2') + act(() => failed.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))) + expect(failed.getAttribute('aria-pressed')).toBe('false') + expect(completedArc.getAttribute('opacity')).toBe('1') + act(() => failed.focus()) + expect(completedArc.getAttribute('opacity')).toBe('0.2') + act(() => failed.blur()) + expect(completedArc.getAttribute('opacity')).toBe('1') + act(() => failed.click()) + act(() => root.render()) + expect(completedArc.getAttribute('opacity')).toBe('1') + expect(container.querySelector('text')?.textContent).toBe('90') + }) + + it.each([1, 2])('labels short daily series with calendar dates (%s buckets)', (count) => { + const svg = mountAtWidth( + 400, + + ) + expect(svg.textContent).toContain('Jan 1') + expect(svg.textContent).not.toContain('00:00') + if (count === 2) expect(svg.textContent).toContain('Jan 2') + }) + + it('reserves the same chart frame for loading, errors, and data', () => { + const content = ( + + ) + mountAtWidth( + 320, + + {content} + + ) + expect(container.querySelector('section')?.getAttribute('aria-busy')).toBe('true') + expect(container.querySelector('svg')?.getAttribute('height')).toBe('160') + expect(container.querySelector('circle')).toBeNull() + act(() => + root.render( + + {content} + + ) + ) + expect(container.querySelector('svg')?.getAttribute('height')).toBe('160') + expect(container.querySelector('[role="alert"]')?.textContent).toBe('Unavailable') + act(() => + root.render( + + {content} + + ) + ) + expect(container.querySelector('svg')?.getAttribute('height')).toBe('160') + expect(container.textContent).toContain('Completed') + expect( + container.querySelector('circle[stroke-dasharray]')?.getAttribute('stroke-dasharray') + ).toBe('100 0') + }) + + it('recovers when a selected line series disappears during refresh', () => { + const data = dailySeries(3, 10) + mountAtWidth( + 400, + + ) + const extra = [...container.querySelectorAll('button')].find((button) => + button.textContent?.includes('Extra') + ) + expect(extra).toBeDefined() + act(() => extra?.click()) + act(() => extra?.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }))) + act(() => root.render()) + expect(container.querySelector('path[stroke="blue"]')?.getAttribute('opacity')).toBe('1') + act(() => + container + .querySelector('svg') + ?.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, clientX: 60, clientY: 40 })) + ) + expect(container.textContent).toContain('Runs') + expect(container.querySelector('table')?.textContent).toContain('10') + }) +}) diff --git a/packages/emcn/src/components/charts/chart-legend.tsx b/packages/emcn/src/components/charts/chart-legend.tsx new file mode 100644 index 00000000000..4beeef6fd1a --- /dev/null +++ b/packages/emcn/src/components/charts/chart-legend.tsx @@ -0,0 +1,82 @@ +'use client' + +import { Chip, cn } from '@sim/emcn' +import { cva, type VariantProps } from 'class-variance-authority' + +export const chartLegendVariants = cva('flex min-w-0 flex-1 gap-0.5', { + variants: { + layout: { column: 'flex-col', row: 'flex-wrap items-center' }, + }, + defaultVariants: { layout: 'column' }, +}) + +export interface ChartLegendItem { + id: string + label: string + color: string + value?: string +} + +interface ChartLegendProps extends VariantProps { + items: ChartLegendItem[] + selectedId: string | null + highlightedId: string | null + onHighlight(id: string | null): void + onSelect(id: string | null): void +} + +/** Hover and focus preview a series; activation holds it until toggled or escaped. */ +export function ChartLegend({ + items, + selectedId, + highlightedId, + onHighlight, + onSelect, + layout, +}: ChartLegendProps) { + return ( +
    + {items.map((item) => ( +
  • + onHighlight(item.id)} + onMouseLeave={() => onHighlight(null)} + onFocus={() => onHighlight(item.id)} + onBlur={() => onHighlight(null)} + onClick={() => onSelect(selectedId === item.id ? null : item.id)} + onKeyDown={(event) => { + if (event.key === 'Escape') { + onSelect(null) + onHighlight(null) + } + }} + leftAdornment={ + + } + rightAdornment={ + item.value ? ( + + {item.value} + + ) : undefined + } + > + {item.label} + +
  • + ))} +
+ ) +} diff --git a/apps/sim/components/charts/chart-tooltip.test.ts b/packages/emcn/src/components/charts/chart-tooltip.test.ts similarity index 85% rename from apps/sim/components/charts/chart-tooltip.test.ts rename to packages/emcn/src/components/charts/chart-tooltip.test.ts index 0f414b14a5f..2bcf452fe3f 100644 --- a/apps/sim/components/charts/chart-tooltip.test.ts +++ b/packages/emcn/src/components/charts/chart-tooltip.test.ts @@ -1,13 +1,15 @@ /** * @vitest-environment node */ -import { describe, expect, it } from 'vitest' -import { CHART_PADDING, resolveChartPadding } from '@/components/charts/chart-geometry' + import { + CHART_PADDING, estimateTooltipHeight, estimateTooltipWidth, positionChartTooltip, -} from '@/components/charts/chart-tooltip' + resolveChartPadding, +} from '@sim/emcn' +import { describe, expect, it } from 'vitest' const WIDTH = 800 const HEIGHT = 166 @@ -92,13 +94,7 @@ describe('estimateTooltipHeight', () => { expect(estimateTooltipHeight(0, false)).toBe(estimateTooltipHeight(1, false)) }) - /** - * The estimate is what the clamp measures against, and the chart clips its overflow, - * so it must never come in under the real box — an underestimate cuts the bottom off - * rather than moving the box up. Measured here against the box model the tooltip's - * own class string implies: `border` + `py-1.5`, a `text-micro` date with `mb-1`, - * and one `text-xs` row per value, every line at the ambient 1.5 line-height. - */ + /** Estimates must cover the rendered box, including inherited line-height and padding. */ it('never comes in under the box the tooltip actually renders', () => { const chrome = 2 + 6 + 6 const dateLine = 10 * 1.5 + 4 diff --git a/apps/sim/components/charts/chart-tooltip.tsx b/packages/emcn/src/components/charts/chart-tooltip.tsx similarity index 64% rename from apps/sim/components/charts/chart-tooltip.tsx rename to packages/emcn/src/components/charts/chart-tooltip.tsx index 518a470479a..9350bc8d97a 100644 --- a/apps/sim/components/charts/chart-tooltip.tsx +++ b/packages/emcn/src/components/charts/chart-tooltip.tsx @@ -1,13 +1,8 @@ 'use client' import type { ReactNode } from 'react' -import { CHART_PADDING, type ChartPadding } from '@/components/charts/chart-geometry' +import { CHART_PADDING, type ChartPadding } from '@sim/emcn' -/** - * The chart family's hover surface. Defined once so a sibling chart cannot ship a - * tooltip that looks almost the same — this class string was previously duplicated - * between the line chart and the status bar. - */ export const CHART_TOOLTIP_CLASSES = 'pointer-events-none absolute rounded-lg border border-[var(--border)] bg-[var(--surface-1)] px-2 py-1.5 text-xs shadow-overlay' @@ -22,15 +17,7 @@ interface PositionChartTooltipArgs { padding?: ChartPadding } -/** - * Places the tooltip beside the cursor, preferring the right and flipping left when - * it would overflow, then clamping it wholly inside the chart box. - * - * The vertical clamp is against the tooltip's own height rather than a fixed inset. - * A fixed one let the box hang a pixel or two past the bottom near the foot of the - * plot, and because the scroll container's `overflow-x` forces `overflow-y` to `auto`, - * those pixels raised a vertical scrollbar the moment the cursor approached the axis. - */ +/** Flip beside the cursor and clamp the whole tooltip inside the chart. */ export function positionChartTooltip({ anchorX, anchorY, @@ -61,26 +48,13 @@ export function estimateTooltipWidth(longestRowLength: number): number { /** Border plus the `py-1.5` the tooltip's own class string sets. */ const TOOLTIP_CHROME_HEIGHT = 2 + 12 -/** - * The `text-micro` date's line box plus its `mb-1`. - * - * The type scale pairs no line-height with a font size, so a line occupies the - * ambient 1.5 rather than the font size itself — 15px for 10px `text-micro`, not 10. - */ +/** Date line height plus its bottom margin; text inherits a 1.5 line-height. */ const TOOLTIP_DATE_HEIGHT = 15 + 4 /** One `text-xs` row's line box: 11px at the ambient 1.5, rounded up from 16.5. */ const TOOLTIP_ROW_HEIGHT = 17 -/** - * Height of the box {@link ChartTooltip} renders, from its own box model. - * - * Estimated rather than measured because the position is computed in the same render - * that mounts the tooltip — reading a real height would need a second paint, which - * shows up as the tooltip visibly jumping under the cursor. Every part rounds up: - * this is what {@link positionChartTooltip} clamps against and the chart clips its - * overflow, so an underestimate cuts the bottom off the box rather than moving it. - */ +/** Estimate height before mounting to avoid repositioning; round up to prevent clipping. */ export function estimateTooltipHeight(rowCount: number, hasDate: boolean): number { return ( TOOLTIP_CHROME_HEIGHT + diff --git a/packages/emcn/src/components/charts/dashboard-metric.tsx b/packages/emcn/src/components/charts/dashboard-metric.tsx new file mode 100644 index 00000000000..a269aca6039 --- /dev/null +++ b/packages/emcn/src/components/charts/dashboard-metric.tsx @@ -0,0 +1,42 @@ +'use client' + +import { Tooltip } from '@sim/emcn' + +interface DashboardMetricProps { + label: string + value: string + description?: string + loading?: boolean +} + +/** A compact metric with a fixed-height value and optional definition. */ +export function DashboardMetric({ label, value, description, loading }: DashboardMetricProps) { + return ( +
+
+ {description ? ( + + + + + {description} + + ) : ( +

{label}

+ )} +
+
+ {loading ? ( + <> +
+
+ ) +} diff --git a/packages/emcn/src/components/charts/donut-chart.tsx b/packages/emcn/src/components/charts/donut-chart.tsx new file mode 100644 index 00000000000..b3c9d362a68 --- /dev/null +++ b/packages/emcn/src/components/charts/donut-chart.tsx @@ -0,0 +1,84 @@ +'use client' + +import { useState } from 'react' +import { ChartLegend } from '@sim/emcn' + +export interface DonutChartSegment { + label: string + value: number + color: string + display?: string +} + +interface DonutChartProps { + segments: DonutChartSegment[] + label: string + totalLabel?: string +} + +/** Shows a part-to-whole distribution, including single-category and empty totals. */ +export function DonutChart({ segments, label, totalLabel }: DonutChartProps) { + const [hoveredLabel, setHoveredLabel] = useState(null) + const [selectedLabel, setSelectedLabel] = useState(null) + const selected = segments.find((segment) => segment.label === selectedLabel) + const highlighted = segments.find((segment) => segment.label === hoveredLabel) ?? selected + const values = segments.filter((segment) => Number.isFinite(segment.value) && segment.value > 0) + const total = values.reduce((sum, segment) => sum + segment.value, 0) + let offset = 0 + return ( +
+ + {label} + + {values.map((segment) => { + const share = (segment.value / total) * 100 + const start = offset + offset += share + return ( + setHoveredLabel(segment.label)} + onMouseLeave={() => setHoveredLabel(null)} + transform='rotate(-90 70 70)' + > + {`${segment.label}: ${segment.display ?? segment.value.toLocaleString()} (${share.toFixed(1)}%)`} + + ) + })} + + {highlighted + ? (highlighted.display ?? + new Intl.NumberFormat(undefined, { notation: 'compact' }).format(highlighted.value)) + : (totalLabel ?? + new Intl.NumberFormat(undefined, { notation: 'compact' }).format(total))} + + + {segments.length > 0 ? ( + ({ + ...segment, + id: segment.label, + value: segment.display ?? segment.value.toLocaleString(), + }))} + selectedId={selected?.label ?? null} + highlightedId={highlighted?.label ?? null} + onHighlight={setHoveredLabel} + onSelect={setSelectedLabel} + /> + ) : ( +

No activity

+ )} +
+ ) +} diff --git a/packages/emcn/src/components/charts/index.ts b/packages/emcn/src/components/charts/index.ts new file mode 100644 index 00000000000..1d8b4b995a4 --- /dev/null +++ b/packages/emcn/src/components/charts/index.ts @@ -0,0 +1,12 @@ +export { BarChart, type BarChartPoint } from './bar-chart' +export { ChartDataTable } from './chart-data-table' +export * from './chart-format' +export { ChartFrame } from './chart-frame' +export * from './chart-geometry' +export { ChartLegend, type ChartLegendItem, chartLegendVariants } from './chart-legend' +export * from './chart-tooltip' +export { DashboardMetric } from './dashboard-metric' +export { DonutChart, type DonutChartSegment } from './donut-chart' +export { LineChart, type LineChartMultiSeries, type LineChartPoint } from './line-chart' +export { RadarChart, type RadarChartAxis } from './radar-chart' +export { useChartWidth, useIsDarkTheme } from './use-chart-theme' diff --git a/apps/sim/components/charts/line-chart.tsx b/packages/emcn/src/components/charts/line-chart.tsx similarity index 68% rename from apps/sim/components/charts/line-chart.tsx rename to packages/emcn/src/components/charts/line-chart.tsx index 0896f1df4ee..29f7ef2d673 100644 --- a/apps/sim/components/charts/line-chart.tsx +++ b/packages/emcn/src/components/charts/line-chart.tsx @@ -1,36 +1,31 @@ 'use client' import { memo, useId, useMemo, useState } from 'react' -import { Button, cn } from '@sim/emcn' -import { - formatChartCompactNumber, - formatChartLatency, - formatChartTimestamp, -} from '@/components/charts/chart-format' import { CHART_AXIS_LABEL_GAP, CHART_DEFAULT_HEIGHT, CHART_GRID_FRACTIONS, CHART_TICK_FILL, CHART_TICK_FONT_SIZE, - chartPlotBand, - formatTimeTick, - resolveChartPadding, - resolveSpanMs, - resolveTimeTickIndices, -} from '@/components/charts/chart-geometry' -import { + ChartDataTable, + ChartLegend, ChartTooltip, ChartTooltipRow, + chartPlotBand, + cn, estimateTooltipHeight, estimateTooltipWidth, + formatChartCompactNumber, + formatChartLatency, + formatChartTimestamp, + formatTimeTick, positionChartTooltip, -} from '@/components/charts/chart-tooltip' -import { + resolveChartPadding, + resolveSpanMs, + resolveTimeTickIndices, useChartWidth, useIsDarkTheme, - useResolvedChartColors, -} from '@/components/charts/use-chart-theme' +} from '@sim/emcn' export interface LineChartPoint { timestamp: string @@ -38,7 +33,7 @@ export interface LineChartPoint { } export interface LineChartMultiSeries { - id?: string + id: string label: string color: string data: LineChartPoint[] @@ -55,14 +50,7 @@ interface LineChartProps { height?: number } -/** - * Smoothed path through `points`, with every control point clamped into the plot - * band so a curve between two near-axis samples cannot bow over an axis rule. - * - * At module scope because the base line and each extra series need the identical - * curve: the two copies had drifted apart before, and a clamp fixed in one drew a - * different shape from the other. - */ +/** Clamp control points to keep smoothed curves within the plot band. */ function buildSmoothPath( points: ReadonlyArray<{ x: number; y: number }>, yMin: number, @@ -95,37 +83,28 @@ function LineChartComponent({ series, height = CHART_DEFAULT_HEIGHT, }: LineChartProps) { - /* - `useId`, not `useRef(generateShortId())`: a ref initializer is evaluated on - every render and all but the first result thrown away, and React already has - a hook whose whole job is a stable unique id. - */ const uniqueId = useId().replace(/:/g, '') const [containerRef, containerWidth] = useChartWidth() const width = containerWidth ?? 0 const isDark = useIsDarkTheme() - const [hoverSeriesId, setHoverSeriesId] = useState(null) - const [activeSeriesId, setActiveSeriesId] = useState(null) + const [legendHoverSeriesId, setLegendHoverSeriesId] = useState(null) + const [selectedSeriesId, setSelectedSeriesId] = useState(null) const [hoverPos, setHoverPos] = useState<{ x: number; y: number } | null>(null) - const colorTokens: Record = { base: color } - for (const s of series ?? []) { - const id = s.id || s.label || '' - if (id) colorTokens[id] = s.color - } - const resolvedColors = useResolvedChartColors(colorTokens) - const hasExternalWrapper = !label || label === '' const allSeries = useMemo( - () => - (Array.isArray(series) && series.length > 0 - ? [{ id: 'base', label, color, data }, ...series] - : [{ id: 'base', label, color, data }] - ).map((s, idx) => ({ ...s, id: s.id || s.label || String(idx) })), + () => [ + { id: 'base', sourceId: 'base', label, color, data }, + ...(series ?? []).map((item) => ({ ...item, id: `series:${item.id}`, sourceId: item.id })), + ], [series, label, color, data] ) + const activeSeriesId = allSeries.some((item) => item.id === selectedSeriesId) + ? selectedSeriesId + : null + const { maxValue, minValue, valueRange } = useMemo(() => { const flatValues = allSeries.flatMap((s) => s.data.map((d) => d.value)) const rawMax = Math.max(...flatValues, 1) @@ -156,10 +135,6 @@ function LineChartComponent({ } }, [allSeries, unit]) - /** - * The two y-axis tick labels, resolved once so the gutter that has to hold them is - * measured from the same strings the axis draws. - */ const yAxisLabels = useMemo(() => { const unitSuffix = (unit || '').trim() const isLatency = unitSuffix.toLowerCase() === 'latency' @@ -189,15 +164,7 @@ function LineChartComponent({ [data, chartWidth, chartHeight, minValue, valueRange, yMin, yMax, padding.left, padding.top] ) - /** - * The hovered sample, derived from the stored cursor rather than stored beside it. - * - * Clamped here rather than relying on the stored x having been clamped at mousemove - * time: `padding.left` follows the axis labels and `chartWidth` follows the - * container, so either can move with no pointer event at all — a sidebar collapse - * mid-hover otherwise pushed the ratio past 1 and indexed off the end, and the dot, - * the rule and the tooltip all vanished until the cursor moved again. - */ + /** Re-clamp the cursor after layout changes, which can occur without a pointer event. */ const hoverIndex = hoverPos === null || scaledPoints.length === 0 ? null @@ -236,10 +203,32 @@ function LineChartComponent({ ] ) + const validLegendHoverId = + scaledSeries.find((item) => item.id === legendHoverSeriesId)?.id ?? null + let hoverSeriesId = validLegendHoverId + if (hoverPos && hoverIndex !== null) { + hoverSeriesId = activeSeriesId + if (!activeSeriesId) { + let nearestDistance = Number.POSITIVE_INFINITY + let nearestSeriesId: string | null = null + for (const item of scaledSeries.slice(1)) { + const point = item.pts[hoverIndex] + if (!point) continue + const distance = Math.abs(point.y - hoverPos.y) + if (distance < nearestDistance) { + nearestDistance = distance + nearestSeriesId = item.id + } + } + hoverSeriesId = nearestDistance <= 12 ? nearestSeriesId : null + } + } + const getSeriesById = (id?: string | null) => scaledSeries.find((s) => s.id === id) - const visibleSeries = activeSeriesId - ? scaledSeries.filter((s) => s.id === activeSeriesId) - : scaledSeries + const visibleSeries = + activeSeriesId && !validLegendHoverId + ? scaledSeries.filter((s) => s.id === activeSeriesId) + : scaledSeries const pathD = useMemo(() => buildSmoothPath(scaledPoints, yMin, yMax), [scaledPoints, yMin, yMax]) @@ -262,16 +251,11 @@ function LineChartComponent({ if (data.length === 0) { return (

No data

@@ -283,13 +267,7 @@ function LineChartComponent({

{label}

{allSeries.length > 1 && ( -
- {scaledSeries.slice(1).map((s) => { - const isActive = activeSeriesId ? activeSeriesId === s.id : true - const isHovered = hoverSeriesId === s.id - const dimmed = activeSeriesId ? !isActive : false - return ( - - ) - })} -
+ )}
)}
+ ({ + id: item.id, + label: item.label || unit || 'Value', + data: item.data, + }))} + />
} - /* + /** Three axes are the fewest that enclose an area; below that the "polygon" is a line or a point and reads as a rendering fault rather than as a distribution. */ @@ -153,32 +107,18 @@ function RadarChartComponent({ axes, color, height = 200 }: RadarChartProps) { const hovered = hoverIndex !== null ? points[hoverIndex] : null return ( - /* - Two boxes, like the siblings: the outer one scrolls, the inner one is the - positioning context. `relative` on the scroll container itself left the - absolutely-positioned tooltip anchored to the viewport of the scroll rather than - to the plot — below CHART_MIN_WIDTH it stayed nailed while the web slid under it. - - Captions are inside the plot by construction, since `radius` is budgeted against - `labelWidth`, so the horizontal scroll never cuts one off. - */ + /** Anchor tooltips to the plot so they scroll with it. */
- {/* - Radial rather than the siblings' vertical linear gradient — a shape with - radial symmetry lit from the top reads as a rendering error. The stop - opacities stay in the family's range, and light is the more opaque theme - because dark composites through `screen` below. - */} - - + + - {RING_FRACTIONS.map((fraction) => ( + {[...CHART_GRID_FRACTIONS, 1].map((fraction) => ( point.value))} fill={`url(#radar-${uniqueId})`} - stroke={resolvedColor} + stroke={color} strokeWidth={isDark ? 1.7 : 2} strokeLinejoin='round' /> @@ -217,27 +157,17 @@ function RadarChartComponent({ axes, color, height = 200 }: RadarChartProps) { cx={point.value.x} cy={point.value.y} r={hoverIndex === index ? 3 : 2} - fill={resolvedColor} + fill={color} /> ))} - {points.map((point, index) => ( + {points.map((point) => ( cx ? 'start' : 'end' } @@ -255,18 +185,7 @@ function RadarChartComponent({ axes, color, height = 200 }: RadarChartProps) { ))} - {/* - Hit targets last so they sit above the painted web, and wedge-sized — a - vertex-sized target is far too small to hover on a 200px chart. - - An arc sector, not a triangle. A triangle's far edge is the chord, which - along its own spoke reaches only `reach·cos(π/n)` — at three axes that is - 50px against a 74px radius, so the largest value's vertex, the one a reader - aims at, sat outside its own target and outside every other. Sectors tile - identically and reach `reach` in every direction. The sweep flag is 1 - because SVG's y grows downward, and the arc is never a major one: 2π/n ≤ - 2π/3 < π for the three-or-more axes this chart requires. - */} + {/** Arc sectors cover the outer vertices; triangular targets leave gaps at low axis counts. */} {points.map((point, index) => { const half = Math.PI / axes.length const angle = (index / axes.length) * Math.PI * 2 - Math.PI / 2 @@ -294,14 +213,7 @@ function RadarChartComponent({ axes, color, height = 200 }: RadarChartProps) { {hovered && (() => { const value = hovered.axis.display ?? String(hovered.axis.value) - /* - Beside the hovered vertex, through the same placer the siblings use, so - the box flips and clamps identically. Centring it on the web instead put - a filled panel over the densest part of the gradient — the concentration - this chart exists to show. The padding passed is the caption gap rather - than the axis-bearing charts' gutters: a radar has no axis rules to keep - clear of. - */ + /** Place tooltips beside the vertex, using caption clearance instead of axis gutters. */ const { left, top } = positionChartTooltip({ anchorX: hovered.value.x, anchorY: hovered.value.y, @@ -315,7 +227,7 @@ function RadarChartComponent({ axes, color, height = 200 }: RadarChartProps) { }) return ( - + ) })()} diff --git a/packages/emcn/src/components/charts/use-chart-theme.ts b/packages/emcn/src/components/charts/use-chart-theme.ts new file mode 100644 index 00000000000..66ae0158630 --- /dev/null +++ b/packages/emcn/src/components/charts/use-chart-theme.ts @@ -0,0 +1,49 @@ +'use client' + +import { type RefObject, useEffect, useRef, useState, useSyncExternalStore } from 'react' +import { CHART_MIN_WIDTH } from '@sim/emcn' + +function subscribeToDarkTheme(onStoreChange: () => void): () => void { + const observer = new MutationObserver(onStoreChange) + observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }) + return () => observer.disconnect() +} + +function getDarkThemeSnapshot(): boolean { + return document.documentElement.classList.contains('dark') +} + +/** Server fallback until the document theme is available. */ +function getServerDarkThemeSnapshot(): boolean { + return true +} + +/** Subscribe to the root theme class for SVG opacity and blend-mode values. */ +export function useIsDarkTheme(): boolean { + return useSyncExternalStore( + subscribeToDarkTheme, + getDarkThemeSnapshot, + getServerDarkThemeSnapshot + ) +} + +/** Observe width with a minimum plot size; null reserves the chart frame before measurement. */ +export function useChartWidth(): [RefObject, number | null] { + const containerRef = useRef(null) + const [width, setWidth] = useState(null) + + useEffect(() => { + const element = containerRef.current + if (!element) return + const observer = new ResizeObserver((entries) => { + const measured = entries[0]?.contentRect?.width + if (measured && measured > 0) setWidth(Math.max(CHART_MIN_WIDTH, Math.floor(measured))) + }) + observer.observe(element) + const rect = element.getBoundingClientRect() + if (rect?.width > 0) setWidth(Math.max(CHART_MIN_WIDTH, Math.floor(rect.width))) + return () => observer.disconnect() + }, []) + + return [containerRef, width] +} diff --git a/packages/emcn/src/components/table/table.tsx b/packages/emcn/src/components/table/table.tsx index 0ba67ba490f..6717785bc2e 100644 --- a/packages/emcn/src/components/table/table.tsx +++ b/packages/emcn/src/components/table/table.tsx @@ -1,6 +1,21 @@ import * as React from 'react' +import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '../../lib/cn' +export const tableVariants = cva('w-full caption-bottom text-small', { + variants: { + variant: { + default: '', + list: 'border-separate border-spacing-x-0 border-spacing-y-0.5 [&_tr]:border-0 [&_th]:h-8 [&_th]:p-2 [&_th]:font-normal [&_th]:text-[var(--text-muted)] [&_th]:text-caption [&_td]:p-2 [&_td]:text-[var(--text-muted)] [&_td]:text-caption [&_td:first-child]:rounded-l-lg [&_td:last-child]:rounded-r-lg', + }, + }, + defaultVariants: { variant: 'default' }, +}) + +interface TableProps + extends React.HTMLAttributes, + VariantProps {} + /** * A simple Table component for displaying data. * @@ -22,10 +37,10 @@ import { cn } from '../../lib/cn' * * ``` */ -const Table = React.forwardRef>( - ({ className, ...props }, ref) => ( +const Table = React.forwardRef( + ({ className, variant, ...props }, ref) => (
- +
) ) diff --git a/packages/emcn/src/index.ts b/packages/emcn/src/index.ts index 606265b1994..f89fbb7afd2 100644 --- a/packages/emcn/src/index.ts +++ b/packages/emcn/src/index.ts @@ -5,6 +5,7 @@ export * from './components' * the COMPONENT; the icon stays available from `@sim/emcn/icons`. */ export { Calendar, type CalendarProps } from './components/calendar/calendar' +export * from './components/charts' /** * `Code` exists in BOTH `./components` (the code editor) and `./icons` (a * glyph). Same resolution as `Calendar` and `Table`: the barrel yields the @@ -40,6 +41,7 @@ export { TableHead, TableHeader, TableRow, + tableVariants, } from './components/table/table' export { type ClipboardContent, diff --git a/scripts/check-tool-registry-boundary.baseline.json b/scripts/check-tool-registry-boundary.baseline.json index a8acbca2503..7f6ee38c1af 100644 --- a/scripts/check-tool-registry-boundary.baseline.json +++ b/scripts/check-tool-registry-boundary.baseline.json @@ -496,16 +496,16 @@ "gateways": {} }, "app/workspace/[workspaceId]/settings/usage/events/page.tsx": { - "modules": 1562, + "modules": 1595, "gateways": { - "apps/sim/lib/auth/index.ts": 1413, + "apps/sim/lib/auth/index.ts": 1441, "apps/sim/triggers/index.ts": 487, "apps/sim/triggers/registry.ts": 485, "apps/sim/blocks/registry.ts": 363, "apps/sim/blocks/registry-maps.ts": 360, - "apps/sim/lib/webhooks/providers/index.ts": 121, - "apps/sim/lib/webhooks/providers/registry.ts": 118, - "apps/sim/lib/workflows/lifecycle.ts": 50 + "apps/sim/lib/webhooks/providers/index.ts": 120, + "apps/sim/lib/webhooks/providers/registry.ts": 117, + "apps/sim/lib/workflows/lifecycle.ts": 52 } }, "app/workspace/[workspaceId]/skills/[skillId]/page.tsx": { From f6bc72378c2c93eb207c00e7e144c2ffe671cf23 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 16 Sep 2026 12:52:10 -0700 Subject: [PATCH 16/43] fix(security): close fail-open paths found by the integration suite (#7886) - Keep an organization's permission groups governing while its payment is failing: enforcement read the usable-subscription set, so a past-due card resolved to "no permission group", which denies nothing and lifted every restriction the organization had configured - Shorten the one-time token lifetime from 24 hours to 2 minutes; the token redeems for a session cookie, so an unredeemed one was a bearer credential for that session until it expired - Refuse the plugin's password-reset endpoints by shape, and refuse the verification sender when it is asked for a reset: both reach the same mailer as the application route without its per-recipient budget - Answer an expired or reused reset link with a 400 and fixed copy rather than a 500 carrying the library's wording, while keeping the request half indistinguishable from a success so it discloses no addresses - Return an invitation token only to callers who may manage the workspace, and stop sending terminal invitations to the client at all - Validate usage dates with the calendar check zod already ships, which cannot throw out of validation the way the hand-rolled round trip did --- apps/sim/app/api/auth/[...all]/route.test.ts | 70 +++++++++++++++++ apps/sim/app/api/auth/[...all]/route.ts | 43 +++++++++++ .../api/auth/forget-password/route.test.ts | 23 +++++- .../sim/app/api/auth/forget-password/route.ts | 24 +++--- .../app/api/auth/reset-password/route.test.ts | 23 +++++- apps/sim/app/api/auth/reset-password/route.ts | 21 +++--- apps/sim/app/api/auth/socket-token/route.ts | 14 ++-- .../sim/app/api/desktop/auth/handoff/route.ts | 16 ++-- .../[id]/permission-groups/utils.ts | 6 ++ .../api/permission-groups/user/route.test.ts | 11 ++- .../api/workspaces/invitations/route.test.ts | 75 ++++++++++++++++++- .../app/api/workspaces/invitations/route.ts | 19 ++++- .../utils/permission-check.test.ts | 10 ++- apps/sim/hooks/queries/invitations.ts | 4 +- apps/sim/lib/api/contracts/invitations.ts | 3 +- .../api/contracts/organization-usage.test.ts | 17 +++++ .../lib/api/contracts/organization-usage.ts | 46 +++--------- apps/sim/lib/auth/auth.ts | 12 ++- apps/sim/lib/auth/better-auth-error.test.ts | 31 ++++++++ apps/sim/lib/auth/better-auth-error.ts | 18 +++++ apps/sim/lib/auth/desktop-handoff.ts | 8 +- .../sim/lib/billing/core/subscription.test.ts | 72 ++++++++++++++++++ apps/sim/lib/billing/core/subscription.ts | 59 ++++++++++++--- apps/sim/lib/billing/index.ts | 1 + apps/sim/lib/invitations/core.ts | 11 ++- .../permission-groups/resolve.server.test.ts | 33 +++----- .../lib/permission-groups/resolve.server.ts | 21 +++--- packages/auth/src/verify.ts | 4 + 28 files changed, 563 insertions(+), 132 deletions(-) create mode 100644 apps/sim/lib/auth/better-auth-error.test.ts create mode 100644 apps/sim/lib/auth/better-auth-error.ts diff --git a/apps/sim/app/api/auth/[...all]/route.test.ts b/apps/sim/app/api/auth/[...all]/route.test.ts index 6257b7f4519..40c8dd45f32 100644 --- a/apps/sim/app/api/auth/[...all]/route.test.ts +++ b/apps/sim/app/api/auth/[...all]/route.test.ts @@ -190,6 +190,76 @@ describe('auth catch-all route (DISABLE_AUTH get-session)', () => { }) }) +describe('auth catch-all route password-reset mail', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it.each([ + 'request-password-reset', + 'email-otp/request-password-reset', + 'forget-password/email-otp', + /** Matched by shape, so a plugin version that renames or adds an alias cannot reopen it. */ + 'request-password-reset/v2', + 'some-plugin/forget-password', + ])('blocks %s, which reaches the mailer without the per-recipient budget', async (path) => { + const req = createMockRequest('POST', undefined, {}, `http://localhost:3000/api/auth/${path}`) + + const res = await POST(req) + + expect(res.status).toBe(404) + expect(handlerMocks.betterAuthPOST).not.toHaveBeenCalled() + await expect(res.json()).resolves.toEqual({ + error: 'Password reset is handled by application API routes.', + }) + }) + + /** The resend button on /verify calls this directly, so blocking it would break verification. */ + it('leaves the verification-code sender reachable for the purpose the product sends', async () => { + const req = createMockRequest( + 'POST', + { email: 'someone@example.com', type: 'email-verification' }, + {}, + 'http://localhost:3000/api/auth/email-otp/send-verification-otp' + ) + + await POST(req) + + expect(handlerMocks.betterAuthPOST).toHaveBeenCalled() + }) + + /** + * The same endpoint takes the OTP purpose from the body, and `forget-password` there sends reset + * mail to any address named — blocking the reset paths while leaving this open renames the hole. + */ + it.each(['forget-password', 'sign-in', 'change-email'])( + 'refuses the verification sender asked for %s', + async (type) => { + const req = createMockRequest( + 'POST', + { email: 'victim@example.com', type }, + {}, + 'http://localhost:3000/api/auth/email-otp/send-verification-otp' + ) + + expect((await POST(req)).status).toBe(404) + expect(handlerMocks.betterAuthPOST).not.toHaveBeenCalled() + } + ) + + it('refuses the verification sender when the body cannot be read', async () => { + const req = createMockRequest( + 'POST', + undefined, + {}, + 'http://localhost:3000/api/auth/email-otp/send-verification-otp' + ) + + expect((await POST(req)).status).toBe(404) + expect(handlerMocks.betterAuthPOST).not.toHaveBeenCalled() + }) +}) + describe('auth catch-all route organization mutations', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/apps/sim/app/api/auth/[...all]/route.ts b/apps/sim/app/api/auth/[...all]/route.ts index 32227242238..b0a80d114d5 100644 --- a/apps/sim/app/api/auth/[...all]/route.ts +++ b/apps/sim/app/api/auth/[...all]/route.ts @@ -15,6 +15,42 @@ export const dynamic = 'force-dynamic' const { GET: betterAuthGET, POST: betterAuthPOST } = toNextJsHandler(auth.handler) const SAFE_ORGANIZATION_POST_PATHS = new Set(['organization/check-slug', 'organization/set-active']) +/** + * Password-reset mail the plugin would send under a name Sim does not own. + * + * `/api/auth/forget-password` is an application route that owns the per-recipient budget (5 per 15 + * minutes, keyed on the address) and writes the `PASSWORD_RESET_REQUESTED` audit record. Every + * plugin alias reaches the same mailer with only a per-IP default in front of it, which a caller + * spread across addresses walks straight past, at one victim's mailbox. Matched rather than listed, + * like the SSO and OAuth guards below, so a plugin version that renames or adds an alias cannot + * quietly reopen the path. + */ +function isBlockedPasswordResetPath(path: string): boolean { + return /(^|\/)(request-password-reset|forget-password)(\/|$)/.test(path) +} + +/** The one OTP purpose a Sim surface sends: the resend button on `/verify`. */ +const ALLOWED_VERIFICATION_OTP_TYPE = 'email-verification' +const VERIFICATION_OTP_SENDER_PATH = 'email-otp/send-verification-otp' + +/** + * The same mailer again, reached by asking the verification sender for a different purpose. + * + * `email-otp/send-verification-otp` takes the OTP `type` from the request body, and + * `forget-password` there sends reset mail to any address named — so blocking the reset paths + * above while leaving this one open would only rename the hole. The endpoint stays reachable for + * the purpose the product actually sends, and an unreadable body is refused rather than forwarded. + */ +async function isBlockedVerificationOtpSend(request: NextRequest, path: string): Promise { + if (path !== VERIFICATION_OTP_SENDER_PATH) return false + // boundary-raw-json: the plugin owns this endpoint's schema; the guard reads one field to decide whether to forward the request at all + const body = await request + .clone() + .json() + .catch(() => null) + return (body as { type?: unknown } | null)?.type !== ALLOWED_VERIFICATION_OTP_TYPE +} + const OAUTH_CALLBACK_PATH_PREFIX = 'oauth2/callback/' const UNSUPPORTED_OIDC_PATHS = new Set([ '.well-known/openid-configuration', @@ -179,6 +215,13 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } + if (isBlockedPasswordResetPath(path) || (await isBlockedVerificationOtpSend(request, path))) { + return NextResponse.json( + { error: 'Password reset is handled by application API routes.' }, + { status: 404 } + ) + } + if (isBlockedOAuthProviderMutationPath(path)) { return NextResponse.json( { error: 'OAuth client registration is not available.' }, diff --git a/apps/sim/app/api/auth/forget-password/route.test.ts b/apps/sim/app/api/auth/forget-password/route.test.ts index d246c5c1543..8c4085b8bb9 100644 --- a/apps/sim/app/api/auth/forget-password/route.test.ts +++ b/apps/sim/app/api/auth/forget-password/route.test.ts @@ -64,6 +64,7 @@ vi.mock('@sim/logger', () => ({ setRequestAuth: vi.fn(), })) +import { APIError } from 'better-auth/api' import { POST } from '@/app/api/auth/forget-password/route' describe('Forget Password API Route', () => { @@ -210,6 +211,24 @@ describe('Forget Password API Route', () => { expect(mockRequestPasswordReset).not.toHaveBeenCalled() }) + /** + * The route answers identically whether or not an account exists, so a refusal must not become a + * status the success path never produces — that alone would tell a caller which addresses are + * registered. It is logged rather than surfaced. + */ + it('answers a refusal Better Auth raises the way it answers a success', async () => { + mockRequestPasswordReset.mockRejectedValue( + new APIError('BAD_REQUEST', { message: 'invalid email' }) + ) + + const response = await POST(createMockRequest('POST', { email: 'someone@example.com' })) + + expect(response.status).toBe(200) + await expect(response.json()).resolves.toEqual({ success: true }) + expect(mockLogger.error).not.toHaveBeenCalled() + expect(mockLogger.warn).toHaveBeenCalled() + }) + it('should handle auth service error with message', async () => { const errorMessage = 'User not found' @@ -223,7 +242,9 @@ describe('Forget Password API Route', () => { const data = await response.json() expect(response.status).toBe(500) - expect(data.message).toBe(errorMessage) + /** An unrecognized failure is ours, and its wording is not for an unauthenticated caller. */ + expect(data.message).toBe('Failed to send password reset email. Please try again later.') + expect(data.message).not.toContain(errorMessage) expect(mockLogger.error).toHaveBeenCalledWith('Error requesting password reset:', { error: expect.any(Error), diff --git a/apps/sim/app/api/auth/forget-password/route.ts b/apps/sim/app/api/auth/forget-password/route.ts index 9f0c7c1ce1f..5e022144431 100644 --- a/apps/sim/app/api/auth/forget-password/route.ts +++ b/apps/sim/app/api/auth/forget-password/route.ts @@ -7,6 +7,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { forgetPasswordContract } from '@/lib/api/contracts' import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' import { auth } from '@/lib/auth' +import { getBetterAuthClientErrorStatus } from '@/lib/auth/better-auth-error' import { enforceIpRateLimitWithIndependentBackstop, enforceRecipientRateLimit, @@ -92,18 +93,23 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ success: true }) } catch (error) { + /** + * A refusal Better Auth raises is not a server fault, but it must not become a distinguishable + * answer either: this route replies identically whether or not an account exists, and a status + * the success path never produces would tell a caller which addresses are registered. So it is + * logged and answered like a success — only the reset half, where the caller already holds the + * token and has nothing left to enumerate, surfaces the refusal. + */ + const clientStatus = getBetterAuthClientErrorStatus(error) + if (clientStatus !== undefined) { + logger.warn('Rejected a password reset request', { status: clientStatus }) + return NextResponse.json({ success: true }) + } + logger.error('Error requesting password reset:', { error }) return NextResponse.json( - { - message: - // utils-lint-allow: returned to an unauthenticated caller, so a non-Error throw - // must surface the fixed copy rather than its own text — getErrorMessage would - // pass a thrown string straight through. - error instanceof Error - ? error.message - : 'Failed to send password reset email. Please try again later.', - }, + { message: 'Failed to send password reset email. Please try again later.' }, { status: 500 } ) } diff --git a/apps/sim/app/api/auth/reset-password/route.test.ts b/apps/sim/app/api/auth/reset-password/route.test.ts index b7038f796fb..67724acd539 100644 --- a/apps/sim/app/api/auth/reset-password/route.test.ts +++ b/apps/sim/app/api/auth/reset-password/route.test.ts @@ -46,6 +46,7 @@ vi.mock('@sim/logger', () => ({ setRequestAuth: vi.fn(), })) +import { APIError } from 'better-auth/api' import { POST } from '@/app/api/auth/reset-password/route' describe('Reset Password API Route', () => { @@ -160,6 +161,22 @@ describe('Reset Password API Route', () => { expect(mockResetPassword).not.toHaveBeenCalled() }) + it('refuses an invalid or expired token with a 400, not a server error', async () => { + // Better Auth reports a consumed, expired, or fabricated token as a 400-class APIError. + // Re-emitting that as a 500 paged on a routine click of a stale reset link. + mockResetPassword.mockRejectedValue(new APIError('BAD_REQUEST', { message: 'invalid token' })) + + const response = await POST( + createMockRequest('POST', { token: 'expired-token', newPassword: 'newSecurePassword123!' }) + ) + + expect(response.status).toBe(400) + await expect(response.json()).resolves.toMatchObject({ + message: 'This reset link is invalid or has expired. Please request a new one.', + }) + expect(mockLogger.error).not.toHaveBeenCalled() + }) + it('should handle auth service error with message', async () => { const errorMessage = 'Invalid or expired token' @@ -174,7 +191,11 @@ describe('Reset Password API Route', () => { const data = await response.json() expect(response.status).toBe(500) - expect(data.message).toBe(errorMessage) + /** An unrecognized failure is ours, and its wording is not for an unauthenticated caller. */ + expect(data.message).toBe( + 'Failed to reset password. Please try again or request a new reset link.' + ) + expect(data.message).not.toContain(errorMessage) expect(mockLogger.error).toHaveBeenCalledWith('Error during password reset:', { error: expect.any(Error), diff --git a/apps/sim/app/api/auth/reset-password/route.ts b/apps/sim/app/api/auth/reset-password/route.ts index 469738fd04f..4bd54347573 100644 --- a/apps/sim/app/api/auth/reset-password/route.ts +++ b/apps/sim/app/api/auth/reset-password/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { resetPasswordContract } from '@/lib/api/contracts' import { parseRequest } from '@/lib/api/server' import { auth } from '@/lib/auth' +import { getBetterAuthClientErrorStatus } from '@/lib/auth/better-auth-error' import { enforceIpRateLimit, type TokenBucketConfig } from '@/lib/core/rate-limiter' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -55,18 +56,20 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ success: true }) } catch (error) { + /** An expired or reused token is the caller's to fix; the fixed copy names the recovery. */ + const clientStatus = getBetterAuthClientErrorStatus(error) + if (clientStatus !== undefined) { + logger.warn('Rejected a password reset', { status: clientStatus }) + return NextResponse.json( + { message: 'This reset link is invalid or has expired. Please request a new one.' }, + { status: 400 } + ) + } + logger.error('Error during password reset:', { error }) return NextResponse.json( - { - message: - // utils-lint-allow: returned to an unauthenticated caller, so a non-Error throw - // must surface the fixed copy rather than its own text — getErrorMessage would - // pass a thrown string straight through. - error instanceof Error - ? error.message - : 'Failed to reset password. Please try again or request a new reset link.', - }, + { message: 'Failed to reset password. Please try again or request a new reset link.' }, { status: 500 } ) } diff --git a/apps/sim/app/api/auth/socket-token/route.ts b/apps/sim/app/api/auth/socket-token/route.ts index 5140d0d62f7..a5afc157e03 100644 --- a/apps/sim/app/api/auth/socket-token/route.ts +++ b/apps/sim/app/api/auth/socket-token/route.ts @@ -3,6 +3,7 @@ import { toError } from '@sim/utils/errors' import { headers } from 'next/headers' import { type NextRequest, NextResponse } from 'next/server' import { auth } from '@/lib/auth' +import { getBetterAuthClientErrorStatus } from '@/lib/auth/better-auth-error' import { isAuthDisabled } from '@/lib/core/config/env-flags' import { enforceIpRateLimit } from '@/lib/core/rate-limiter' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -40,14 +41,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ token: response.token }) } catch (error) { - // better-auth's sessionMiddleware throws APIError("UNAUTHORIZED") with no message - // when the session is missing/expired — surface this as a 401, not a 500. - if ( - error instanceof Error && - ('statusCode' in error || 'status' in error) && - ((error as Record).statusCode === 401 || - (error as Record).status === 'UNAUTHORIZED') - ) { + /** + * better-auth's sessionMiddleware throws `APIError("UNAUTHORIZED")` with no message when the + * session is missing or expired — surface that as a 401, not a 500. + */ + if (getBetterAuthClientErrorStatus(error) === 401) { logger.warn('Socket token request with invalid/expired session') return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) } diff --git a/apps/sim/app/api/desktop/auth/handoff/route.ts b/apps/sim/app/api/desktop/auth/handoff/route.ts index 401da183f6c..f336c54c89f 100644 --- a/apps/sim/app/api/desktop/auth/handoff/route.ts +++ b/apps/sim/app/api/desktop/auth/handoff/route.ts @@ -3,6 +3,7 @@ import { getErrorMessage } from '@sim/utils/errors' import { headers } from 'next/headers' import { type NextRequest, NextResponse } from 'next/server' import { auth } from '@/lib/auth' +import { getBetterAuthClientErrorStatus } from '@/lib/auth/better-auth-error' import { createDesktopHandoffToken } from '@/lib/auth/desktop-handoff' import { enforceIpRateLimit } from '@/lib/core/rate-limiter' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -43,15 +44,12 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const token = await createDesktopHandoffToken(session.user.id) return NextResponse.json({ token }) } catch (error) { - // Session creation runs the app's own `session.create.before` hook, which - // rejects access-controlled accounts with a Better Auth APIError. That is a - // permanent refusal, not a server fault — a 500 would tell the user to try - // again forever. - if ( - error instanceof Error && - 'statusCode' in error && - (error as Record).statusCode === 403 - ) { + /** + * Session creation runs the app's own `session.create.before` hook, which rejects + * access-controlled accounts with a Better Auth `APIError`. That is a permanent refusal, not a + * server fault — a 500 would tell the user to try again forever. + */ + if (getBetterAuthClientErrorStatus(error) === 403) { logger.warn('Desktop handoff refused for this account', { userId: session.user.id }) return NextResponse.json( { error: getErrorMessage(error, 'Access restricted') }, diff --git a/apps/sim/app/api/organizations/[id]/permission-groups/utils.ts b/apps/sim/app/api/organizations/[id]/permission-groups/utils.ts index f96ac8dacf7..cb19b17e163 100644 --- a/apps/sim/app/api/organizations/[id]/permission-groups/utils.ts +++ b/apps/sim/app/api/organizations/[id]/permission-groups/utils.ts @@ -31,6 +31,12 @@ export async function authorizeOrgAccessControl( return NextResponse.json({ error: 'Admin permissions required' }, { status: 403 }) } + /** + * The feature gate, deliberately, not the governance reader: the Access Control settings page is + * gated on the same plan check, so reading governance here would open the API for a past-due + * organization whose page still 404s. Restrictions keep applying through a dunning window — + * that is what the governance reader is for — but managing them follows the page. + */ const entitled = await isOrganizationOnEnterprisePlan(organizationId) if (!entitled) { return NextResponse.json({ error: 'Access Control is an Enterprise feature' }, { status: 403 }) diff --git a/apps/sim/app/api/permission-groups/user/route.test.ts b/apps/sim/app/api/permission-groups/user/route.test.ts index 38455087aea..f58c021e342 100644 --- a/apps/sim/app/api/permission-groups/user/route.test.ts +++ b/apps/sim/app/api/permission-groups/user/route.test.ts @@ -21,6 +21,8 @@ vi.mock('@sim/platform-authz/workspace', async (importOriginal) => ({ vi.mock('@/lib/workspaces/permissions/utils', () => ({ isOrganizationAdminOrOwner: mocks.admin })) vi.mock('@/lib/billing/core/subscription', () => ({ isOrganizationOnEnterprisePlan: mocks.enterprise, + /** Permission resolution reads the governance axis; these tests drive both from one knob. */ + isOrganizationGovernanceActive: mocks.enterprise, })) vi.mock('@/lib/permission-groups/resolve.server', async (importOriginal) => ({ ...(await importOriginal()), @@ -167,10 +169,11 @@ describe('user permission policy shared read', () => { expect(await (await get()).json()).toEqual({ ...unrestricted, entitled: true }) }) it('does not turn policy infrastructure failures into unrestricted access', async () => { - mocks.enterprise.mockImplementation(async (_organizationId, onError) => { - if (onError === 'throw') throw new Error('unavailable') - return false - }) + /** + * The governance reader has no lenient mode — answering `false` on a failed read would mean + * "no permission group", which denies nothing — so a failure here is simply a rejection. + */ + mocks.enterprise.mockRejectedValue(new Error('unavailable')) expect((await get()).status).toBe(500) await expect( readUserPermissionConfig.execute({ principal, input: { workspaceId: 'workspace' } }) diff --git a/apps/sim/app/api/workspaces/invitations/route.test.ts b/apps/sim/app/api/workspaces/invitations/route.test.ts index 2aa469b8ffa..661ad07dfa6 100644 --- a/apps/sim/app/api/workspaces/invitations/route.test.ts +++ b/apps/sim/app/api/workspaces/invitations/route.test.ts @@ -35,7 +35,11 @@ const { mockFindPendingGrantWorkspaceIds, mockFindPendingOrganizationInvitation, mockGetInvitePlanCategoryForUser, + mockListInvitationsForWorkspaces, + mockListAccessibleWorkspaceRowsForUser, } = vi.hoisted(() => ({ + mockListInvitationsForWorkspaces: vi.fn().mockResolvedValue([]), + mockListAccessibleWorkspaceRowsForUser: vi.fn().mockResolvedValue([]), MockConflictingPendingInvitationError: class extends Error {}, mockGetWorkspaceInvitePolicy: vi.fn(), mockValidateInvitationsAllowed: vi.fn().mockResolvedValue(undefined), @@ -89,7 +93,11 @@ vi.mock('@/lib/invitations/send', () => ({ vi.mock('@/lib/invitations/core', () => ({ normalizeEmail: (email: string) => email.trim().toLowerCase(), - listInvitationsForWorkspaces: vi.fn().mockResolvedValue([]), + listInvitationsForWorkspaces: mockListInvitationsForWorkspaces, +})) + +vi.mock('@/lib/workspaces/utils', () => ({ + listAccessibleWorkspaceRowsForUser: mockListAccessibleWorkspaceRowsForUser, })) vi.mock('@/ee/access-control/utils/permission-check', () => ({ @@ -112,6 +120,71 @@ const mockGetWorkspaceWithOwner = permissionsMockFns.mockGetWorkspaceWithOwner import { UPGRADE_TO_INVITE_REASON } from '@/lib/workspaces/policy-constants' import { POST } from '@/app/api/workspaces/invitations/batch/route' +import { GET } from '@/app/api/workspaces/invitations/route' + +describe('GET /api/workspaces/invitations', () => { + const invitation = (workspaceId: string) => ({ + id: `inv-${workspaceId}`, + workspaceId, + email: 'invitee@example.com', + token: `token-${workspaceId}`, + status: 'pending', + permission: 'admin', + }) + + beforeEach(() => { + vi.clearAllMocks() + mockGetSession.mockResolvedValue({ user: { id: 'user-1' } }) + mockListAccessibleWorkspaceRowsForUser.mockResolvedValue([ + { workspace: { id: 'ws-managed' }, permissionType: 'admin', viaOrgAdmin: false }, + /** An org admin: the row reader promotes these to `admin` before the route sees them. */ + { workspace: { id: 'ws-org-admin' }, permissionType: 'admin', viaOrgAdmin: true }, + { workspace: { id: 'ws-read-only' }, permissionType: 'read', viaOrgAdmin: false }, + ]) + mockListInvitationsForWorkspaces.mockResolvedValue([ + invitation('ws-managed'), + invitation('ws-org-admin'), + invitation('ws-read-only'), + ]) + }) + + /** + * The token stands in for being the invitee or an admin on the invitation detail route, which + * answers with the invitee's address and every workspace the invitation grants — so a reader of + * one workspace must not be handed it for every invitation they can see. + */ + it('returns the token only for workspaces the caller may manage', async () => { + const response = await GET(createMockRequest('GET')) + + expect(response.status).toBe(200) + const { invitations } = await response.json() + expect(invitations).toEqual([ + expect.objectContaining({ workspaceId: 'ws-managed', token: 'token-ws-managed' }), + expect.objectContaining({ workspaceId: 'ws-org-admin', token: 'token-ws-org-admin' }), + expect.not.objectContaining({ token: expect.anything() }), + ]) + expect(invitations[2]).toMatchObject({ + workspaceId: 'ws-read-only', + email: 'invitee@example.com', + }) + }) + + it('asks only for the workspaces the caller can reach', async () => { + await GET(createMockRequest('GET')) + + expect(mockListInvitationsForWorkspaces).toHaveBeenCalledWith([ + 'ws-managed', + 'ws-org-admin', + 'ws-read-only', + ]) + }) + + it('refuses an unauthenticated caller', async () => { + mockGetSession.mockResolvedValue(null) + + expect((await GET(createMockRequest('GET'))).status).toBe(401) + }) +}) afterAll(resetEnvFlagsMock) diff --git a/apps/sim/app/api/workspaces/invitations/route.ts b/apps/sim/app/api/workspaces/invitations/route.ts index 101f0dc8fff..8eac267368d 100644 --- a/apps/sim/app/api/workspaces/invitations/route.ts +++ b/apps/sim/app/api/workspaces/invitations/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { omit } from '@sim/utils/object' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -21,9 +22,23 @@ export const GET = withRouteHandler(async (req: NextRequest) => { return NextResponse.json({ invitations: [] }) } - const invitations = await listInvitationsForWorkspaces( - accessibleRows.map((row) => row.workspace.id) + /** + * The token stands in for being the invitee or a workspace admin on + * `GET /api/invitations/[id]`, which answers with the invitee's address, the organization, and + * every workspace the invitation grants. Its one use in the product is the admin-only "Copy + * invite link" — yet every reader received it, for every invitation in every workspace they + * could see. + */ + /** Org admins arrive already promoted to `admin` by the row reader, so this covers them too. */ + const manageableWorkspaceIds = new Set( + accessibleRows.filter((row) => row.permissionType === 'admin').map((row) => row.workspace.id) ) + + const rows = await listInvitationsForWorkspaces(accessibleRows.map((row) => row.workspace.id)) + const invitations = rows.map((invitation) => + manageableWorkspaceIds.has(invitation.workspaceId) ? invitation : omit(invitation, ['token']) + ) + return NextResponse.json({ invitations }) } catch (error) { logger.error('Error fetching workspace invitations:', error) diff --git a/apps/sim/ee/access-control/utils/permission-check.test.ts b/apps/sim/ee/access-control/utils/permission-check.test.ts index 13b0bfc54da..c533905d353 100644 --- a/apps/sim/ee/access-control/utils/permission-check.test.ts +++ b/apps/sim/ee/access-control/utils/permission-check.test.ts @@ -21,6 +21,12 @@ const { mockIsOrganizationOnEnterprisePlan, mockGetWorkspaceWithOwner, mockGetPr vi.mock('@/lib/billing/core/subscription', () => ({ isOrganizationOnEnterprisePlan: mockIsOrganizationOnEnterprisePlan, + /** + * The same knob drives both: these tests ask whether the organization is entitled at all, and + * permission resolution reads the governance axis, which only differs from the feature gate + * while a payment is failing. + */ + isOrganizationGovernanceActive: mockIsOrganizationOnEnterprisePlan, })) vi.mock('@/lib/workspaces/permissions/utils', () => ({ @@ -226,7 +232,7 @@ describe('access control context resolution', () => { await expect(getUserPermissionConfig('user-123', 'workspace-1')).resolves.toMatchObject({ disableMcpTools: true, }) - expect(mockIsOrganizationOnEnterprisePlan).toHaveBeenCalledWith('org-1', 'throw') + expect(mockIsOrganizationOnEnterprisePlan).toHaveBeenCalledWith('org-1') }) it('returns the explicit governing group and its effective config', async () => { @@ -310,7 +316,7 @@ describe('access control context resolution', () => { ) expect(mockGetWorkspaceWithOwner).not.toHaveBeenCalled() - expect(mockIsOrganizationOnEnterprisePlan).toHaveBeenCalledWith('org-verified', 'throw') + expect(mockIsOrganizationOnEnterprisePlan).toHaveBeenCalledWith('org-verified') expect(context).toMatchObject({ organizationId: 'org-verified', entitled: true, diff --git a/apps/sim/hooks/queries/invitations.ts b/apps/sim/hooks/queries/invitations.ts index 33b28996de2..49cc718dfc6 100644 --- a/apps/sim/hooks/queries/invitations.ts +++ b/apps/sim/hooks/queries/invitations.ts @@ -85,7 +85,8 @@ export interface WorkspaceInvitation { isPendingInvitation: boolean isExternal: boolean invitationId?: string - token: string + /** Absent unless the viewer may manage the workspace; the copy-link action is gated on it. */ + token?: string } async function fetchPendingInvitations( @@ -96,6 +97,7 @@ async function fetchPendingInvitations( return ( data.invitations + /** The server returns pending rows only; the status check stays as a cheap contract guard. */ ?.filter( (inv: PendingInvitationRow) => inv.status === 'pending' && inv.workspaceId === workspaceId ) diff --git a/apps/sim/lib/api/contracts/invitations.ts b/apps/sim/lib/api/contracts/invitations.ts index 8c6d12a6030..cbc360f10ab 100644 --- a/apps/sim/lib/api/contracts/invitations.ts +++ b/apps/sim/lib/api/contracts/invitations.ts @@ -40,7 +40,8 @@ export const pendingWorkspaceInvitationSchema = z id: z.string(), workspaceId: z.string(), email: z.string(), - token: z.string(), + /** Present only for a caller who may manage the workspace — it is an admin affordance. */ + token: z.string().optional(), permission: workspacePermissionSchema, membershipIntent: z.enum(['internal', 'external']).optional(), status: z.string(), diff --git a/apps/sim/lib/api/contracts/organization-usage.test.ts b/apps/sim/lib/api/contracts/organization-usage.test.ts index 44e7c1903cd..0b61a6f4559 100644 --- a/apps/sim/lib/api/contracts/organization-usage.test.ts +++ b/apps/sim/lib/api/contracts/organization-usage.test.ts @@ -23,6 +23,23 @@ describe('organization usage window contract', () => { expect(parseWindow({ startDate: '2026-02-30' }).success).toBe(false) }) + /** + * Out of calendar range but well-formed enough to reach the old hand-rolled refinement, which + * called `toISOString` on an Invalid Date. Zod does not wrap refinements, so the RangeError + * escaped `safeParse` itself and every usage route answered a malformed query string with a 500. + */ + it.each(['2026-13-01', '2026-00-01', '2026-01-32', '2026-01-00', '9999-99-99'])( + 'refuses %s without throwing out of safeParse', + (startDate) => { + expect(parseWindow({ startDate }).success).toBe(false) + } + ) + + it('refuses February 29 in a non-leap year', () => { + expect(parseWindow({ startDate: '2026-02-29' }).success).toBe(false) + expect(parseWindow({ startDate: '2024-02-29' }).success).toBe(true) + }) + it('refuses a parseable non-date such as a bare month', () => { // `new Date('2026-08')` is August 1. Accepting it returned a window the caller // never asked for, with nothing to indicate the value had been reinterpreted. diff --git a/apps/sim/lib/api/contracts/organization-usage.ts b/apps/sim/lib/api/contracts/organization-usage.ts index 7c6d60b0d24..dbfeea8ecda 100644 --- a/apps/sim/lib/api/contracts/organization-usage.ts +++ b/apps/sim/lib/api/contracts/organization-usage.ts @@ -46,43 +46,21 @@ export const ORGANIZATION_USAGE_BREAKDOWN_MAX_LIMIT = 100 /** * A bare `YYYY-MM-DD` calendar date, and nothing else. * - * Strict on purpose. The picker sends only bare dates — it has no time component — - * and every looser rule tried here has been wrong in a different way: + * `z.iso.date()` is a calendar check rather than a format one — it refuses `2026-02-30` and a + * non-leap `2026-02-29`, so February is never answered about March — and it is pure pattern + * matching, so no input can make it throw. Both matter: a hand-rolled round trip through + * `toISOString` threw on an out-of-range month, and inside a refinement that escapes validation + * entirely and answers a malformed query string with a 500. * - * - `Date.parse` alone accepts `2026-02-30` and rolls it forward, so February was - * answered about March. The round-trip below is what makes this a *calendar* - * check: a day that does not survive re-serialization never existed. - * - Validating only a `YYYY-MM-DD` prefix let `2026-08` through as August 1, and - * `2026-08-01Tgarbage` through as an `Invalid Date` that made the window resolver - * throw from `toISOString` — a 500 for a malformed query string. - * - A datetime with an offset would validate on its date part while the resolver - * read a different UTC day off the full value, so the range shown and the range - * queried could disagree. - * - * Accepting only the one form the client actually sends removes all three at once. + * Absent is allowed and empty is not. A missing bound is a real state — the picker clears the + * param rather than blanking it, and the resolver falls back to the current period — while an + * explicit `?start-date=` is a malformed request that must not silently answer about a different + * window. Deliberately unlike `usageLimitSchema`, which coerces `''` to its declared default; + * these bounds have none, so omitting one changes which period you get. */ -const isoDateSchema = z - .string() +const isoDateSchema = z.iso + .date({ error: 'Expected a calendar date in YYYY-MM-DD form, such as 2026-08-01' }) .optional() - .refine( - (value) => { - /* - Absent is allowed; empty is not. A missing bound is a real state — the picker - clears the param rather than blanking it — and the resolver falls back to the - current period for it. An explicit `?start-date=` is a malformed request, and - treating it as absent silently answered about a different window than the one - asked for. - - Deliberately unlike `usageLimitSchema`, which does coerce `''` to its default: - that field declares a default, so omission has a documented meaning. These - bounds have none — omitting one changes which period you get. - */ - if (value === undefined) return true - if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false - return new Date(`${value}T00:00:00.000Z`).toISOString().slice(0, 10) === value - }, - { message: 'Expected a calendar date in YYYY-MM-DD form, such as 2026-08-01' } - ) /** * A page size that treats an empty or absent parameter as omitted. diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index db651381b50..7d724bed1ec 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -1191,7 +1191,17 @@ export const auth = betterAuth({ : []), admin(), oneTimeToken({ - expiresIn: 24 * 60, // 24 hours in minutes (better-auth's expiresIn unit) + /** + * Minutes, and deliberately close to zero. A one-time token redeems through + * `/one-time-token/verify`, which answers with a session cookie for the session the + * token points at — so an unredeemed token is a bearer credential for that session + * until it expires, and its lifetime is the only thing bounding that. Nothing here + * needs a long one: the socket handshake mints a fresh token inside the Socket.IO + * `auth` callback and sends it in that same attempt, and the desktop handoff writes its + * own row with its own expiry, which `/one-time-token/verify` reads off the row rather + * than from this option (see lib/auth/desktop-handoff.ts). + */ + expiresIn: 2, }), customSession(async ({ user, session }) => ({ user, diff --git a/apps/sim/lib/auth/better-auth-error.test.ts b/apps/sim/lib/auth/better-auth-error.test.ts new file mode 100644 index 00000000000..92984909a65 --- /dev/null +++ b/apps/sim/lib/auth/better-auth-error.test.ts @@ -0,0 +1,31 @@ +/** + * @vitest-environment node + */ +import { APIError } from 'better-auth/api' +import { describe, expect, it } from 'vitest' +import { getBetterAuthClientErrorStatus } from '@/lib/auth/better-auth-error' + +describe('getBetterAuthClientErrorStatus', () => { + /** + * Built from real `APIError`s rather than stand-ins: the helper exists because the shape belongs + * to a dependency, so a fixture agreeing with our guess would prove nothing about what the + * routes actually catch. + */ + it.each([ + ['BAD_REQUEST' as const, 400], + ['UNAUTHORIZED' as const, 401], + ['FORBIDDEN' as const, 403], + ])('reads %s as %i', (status, expected) => { + expect(getBetterAuthClientErrorStatus(new APIError(status, { message: 'refused' }))).toBe( + expected + ) + }) + + it('says nothing about a server fault, an ordinary error, or a thrown non-error', () => { + expect( + getBetterAuthClientErrorStatus(new APIError('INTERNAL_SERVER_ERROR', { message: 'boom' })) + ).toBeUndefined() + expect(getBetterAuthClientErrorStatus(new Error('connection reset'))).toBeUndefined() + expect(getBetterAuthClientErrorStatus('invalid token')).toBeUndefined() + }) +}) diff --git a/apps/sim/lib/auth/better-auth-error.ts b/apps/sim/lib/auth/better-auth-error.ts new file mode 100644 index 00000000000..7e097754ec5 --- /dev/null +++ b/apps/sim/lib/auth/better-auth-error.ts @@ -0,0 +1,18 @@ +/** + * The 4xx status a Better Auth refusal carries, or `undefined` when the failure is the server's. + * + * Better Auth throws `APIError` for ordinary caller mistakes — an invalid or already-consumed + * reset token, an expired session, a password outside the configured length. A route that catches + * one without reading the status reports a 400-class refusal as a 500: it pages on a routine user + * action and tells the caller the server broke. + * + * Read off the instance rather than with `instanceof`, because `APIError` belongs to a transitive + * dependency and a duplicated copy in the tree would silently defeat the check. + */ +export function getBetterAuthClientErrorStatus(error: unknown): number | undefined { + if (!(error instanceof Error)) return undefined + const statusCode = (error as { statusCode?: unknown }).statusCode + return typeof statusCode === 'number' && statusCode >= 400 && statusCode < 500 + ? statusCode + : undefined +} diff --git a/apps/sim/lib/auth/desktop-handoff.ts b/apps/sim/lib/auth/desktop-handoff.ts index 059013bf0cc..40a321a0243 100644 --- a/apps/sim/lib/auth/desktop-handoff.ts +++ b/apps/sim/lib/auth/desktop-handoff.ts @@ -20,10 +20,10 @@ const HANDOFF_TOKEN_LENGTH = 32 /** * The browser navigates straight to the desktop app's loopback listener once - * the token is minted, so a redeem lands within seconds. Deliberately far - * shorter than the plugin-wide 24h `expiresIn`: this token is a bearer - * credential that grants a session, and `/one-time-token/verify` enforces the - * expiry stored on the row, not the plugin option. + * the token is minted, so a redeem lands within seconds. Kept short because this + * token is a bearer credential that grants a session, and set here rather than + * inherited: `/one-time-token/verify` enforces the expiry stored on the row, so + * this TTL holds whatever the plugin-wide `expiresIn` happens to be. */ const HANDOFF_TOKEN_TTL_MS = 3 * 60 * 1000 diff --git a/apps/sim/lib/billing/core/subscription.test.ts b/apps/sim/lib/billing/core/subscription.test.ts index dbf656a9fef..f9637992983 100644 --- a/apps/sim/lib/billing/core/subscription.test.ts +++ b/apps/sim/lib/billing/core/subscription.test.ts @@ -9,6 +9,7 @@ import { schemaMock, setEnvFlags, } from '@sim/testing' +import { inArray } from 'drizzle-orm' import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -77,6 +78,7 @@ import { hasWorkspaceLiveSyncAccess, hasWorkspaceSandboxAccess, hasWorkspaceSandboxRetentionAccess, + isOrganizationGovernanceActive, isOrganizationOnEnterprisePlan, isWorkspaceOnEnterprisePlan, resolveOrganizationPlan, @@ -586,6 +588,76 @@ describe('resolveOrganizationPlan', () => { }) }) +describe('isOrganizationGovernanceActive', () => { + const ORGANIZATION_ID = 'org-governed' + + beforeEach(() => { + vi.clearAllMocks() + setEnvFlags({ isBillingEnabled: true, isHosted: true }) + mockIsOrganizationBillingBlocked.mockResolvedValue(false) + mockCheckEnterprisePlan.mockReturnValue(true) + }) + + it('governs an organization holding an active enterprise plan', async () => { + dbChainMockFns.limit.mockResolvedValue([{ plan: 'enterprise', status: 'active' }]) + + await expect(isOrganizationGovernanceActive(ORGANIZATION_ID)).resolves.toBe(true) + }) + + /** + * The bug this exists for: an unentitled organization resolves to `config: null`, which denies + * nothing, so treating a failing card as a lapsed plan lifted every restriction the organization + * had configured — silently, for the whole dunning window. + */ + it('keeps governing through a past-due subscription', async () => { + dbChainMockFns.limit.mockResolvedValue([{ plan: 'enterprise', status: 'past_due' }]) + + await expect(isOrganizationGovernanceActive(ORGANIZATION_ID)).resolves.toBe(true) + /** + * Asserted on the filter, not the returned row: the chain mock answers whatever is queued + * regardless of the where clause, so only the status set proves a past-due subscription is + * actually read. The feature gate below deliberately narrows to `active`. + */ + expect(vi.mocked(inArray)).toHaveBeenCalledWith( + expect.anything(), + expect.arrayContaining(['active', 'past_due']) + ) + }) + + it('reads a narrower status set than the feature gate does', async () => { + dbChainMockFns.limit.mockResolvedValue([{ plan: 'enterprise', status: 'active' }]) + + await isOrganizationOnEnterprisePlan('org-feature-gate') + expect(vi.mocked(inArray)).not.toHaveBeenCalledWith( + expect.anything(), + expect.arrayContaining(['past_due']) + ) + }) + + /** A suspension is a billing state, not a decision to stop governing. */ + it('keeps governing a billing-blocked organization', async () => { + mockIsOrganizationBillingBlocked.mockResolvedValue(true) + dbChainMockFns.limit.mockResolvedValue([{ plan: 'enterprise', status: 'past_due' }]) + + await expect(isOrganizationGovernanceActive(ORGANIZATION_ID)).resolves.toBe(true) + }) + + it('stops governing an organization with no subscription at all', async () => { + dbChainMockFns.limit.mockResolvedValue([]) + + await expect(isOrganizationGovernanceActive(ORGANIZATION_ID)).resolves.toBe(false) + }) + + /** A read failure must never read as "no restrictions". */ + it('propagates a failed subscription read rather than answering false', async () => { + dbChainMockFns.limit.mockRejectedValue(new Error('billing database unavailable')) + + await expect(isOrganizationGovernanceActive(ORGANIZATION_ID)).rejects.toThrow( + 'billing database unavailable' + ) + }) +}) + describe('isOrganizationOnEnterprisePlan', () => { const ORGANIZATION_ID = 'org-1' diff --git a/apps/sim/lib/billing/core/subscription.ts b/apps/sim/lib/billing/core/subscription.ts index 04e8556c94e..10b709b54bd 100644 --- a/apps/sim/lib/billing/core/subscription.ts +++ b/apps/sim/lib/billing/core/subscription.ts @@ -157,9 +157,9 @@ export async function syncSubscriptionPlan( } /** - * Get the organization's subscription row when its status is one of - * `USABLE_SUBSCRIPTION_STATUSES` (product access — stricter than - * `ENTITLED_SUBSCRIPTION_STATUSES` which also includes `past_due`). + * Get the organization's subscription row when its status is one of `statuses`, which defaults to + * `USABLE_SUBSCRIPTION_STATUSES` (product access — stricter than `ENTITLED_SUBSCRIPTION_STATUSES`, + * which also includes `past_due`). * Use this for feature-gating ("can this org use the product right * now"). Use `getOrganizationSubscription` (from `core/billing.ts`) * when you need the billing-side entitlement row that includes @@ -168,13 +168,22 @@ export async function syncSubscriptionPlan( interface GetOrganizationSubscriptionUsableOptions { onError?: 'return-null' | 'throw' executor?: DbOrTx + /** + * Which statuses count. Defaults to the usable set; a caller that governs behavior rather than + * granting a feature passes the entitled set, so a dunning window does not read as no plan. + */ + statuses?: readonly string[] } export async function getOrganizationSubscriptionUsable( organizationId: string, options: GetOrganizationSubscriptionUsableOptions = {} ) { - const { onError = 'return-null', executor = db } = options + const { + onError = 'return-null', + executor = db, + statuses = USABLE_SUBSCRIPTION_STATUSES, + } = options try { const [orgSub] = await executor .select() @@ -182,7 +191,7 @@ export async function getOrganizationSubscriptionUsable( .where( and( eq(subscription.referenceId, organizationId), - inArray(subscription.status, USABLE_SUBSCRIPTION_STATUSES) + inArray(subscription.status, [...statuses]) ) ) .limit(1) @@ -437,12 +446,13 @@ export function isSubscriptionBackedEntitlement(): boolean { * `'return-false'` (the default) fails closed for a *feature* gate: the feature * is hidden, and the worst outcome is a button that is briefly missing. * + * Whether a permission-group regime *applies* is a different axis and is not asked here — see + * {@link isOrganizationGovernanceActive}, where a swallowed failure would lift restrictions. + * * `'throw'` is for callers where "no Enterprise plan" is not a smaller answer - * but a different regime. Access Control resolves to `config: null` when the - * organization is not entitled, and `null` means *every* capability allowed and - * every allowlist off — so a swallowed subscription-read failure would silently - * disable the whole permission-group regime for the request instead of - * surfacing an error. Those callers must pass `'throw'`. + * but a different regime — SCIM deprovisioning and knowledge availability, where answering + * "not entitled" on a failed read would silently widen access rather than narrow it. Those + * callers must pass `'throw'`. * * A primitive rather than an options object on purpose: `cache()` keys on the * argument list, and a fresh object literal per call would miss the memo every @@ -569,6 +579,35 @@ export async function resolveOrganizationPlan( */ export const isOrganizationOnEnterprisePlan = cache(resolveOrganizationEnterprisePlan) +/** + * Whether an organization's permission-group regime governs its members. + * + * Deliberately not {@link isOrganizationOnEnterprisePlan}. That answers "may this organization use + * an Enterprise feature", where withholding the feature during a payment failure is the safe + * direction. Governance is the opposite: an organization that is not entitled resolves to + * `config: null`, and `null` denies nothing — so reading a past-due card as a lapsed plan would + * *lift* every restriction the organization configured, silently, for the whole dunning window. + * + * So this accepts every entitled status rather than only the usable ones, and does not consult the + * billing block: neither an unpaid invoice nor a suspension is a decision to stop governing. Read + * failures always throw for the same reason — a swallowed error would read as "no restrictions". + */ +async function resolveOrganizationGovernancePlan( + organizationId: string, + executor: DbOrTx = db +): Promise { + if (!isSubscriptionBackedEntitlement()) return true + + const orgSub = await getOrganizationSubscriptionUsable(organizationId, { + executor, + onError: 'throw', + statuses: ENTITLED_SUBSCRIPTION_STATUSES, + }) + return !!orgSub && checkEnterprisePlan(orgSub) +} + +export const isOrganizationGovernanceActive = cache(resolveOrganizationGovernancePlan) + /** * Entitlement for a single org-scoped enterprise feature. * diff --git a/apps/sim/lib/billing/index.ts b/apps/sim/lib/billing/index.ts index 68cfa2fea66..1e43f2a866b 100644 --- a/apps/sim/lib/billing/index.ts +++ b/apps/sim/lib/billing/index.ts @@ -13,6 +13,7 @@ export { hasSSOAccess, isEnterpriseOrgAdminOrOwner, isEnterprisePlan as hasEnterprisePlan, + isOrganizationGovernanceActive, isOrganizationOnEnterprisePlan, isProPlan as hasProPlan, isTeamPlan as hasTeamPlan, diff --git a/apps/sim/lib/invitations/core.ts b/apps/sim/lib/invitations/core.ts index 3d40f88e3e8..2316d269116 100644 --- a/apps/sim/lib/invitations/core.ts +++ b/apps/sim/lib/invitations/core.ts @@ -1875,6 +1875,10 @@ export async function listPendingInvitationsForEmail( return Promise.all(rows.map((row) => hydrateInvitation(row))) } +/** + * Pending grants for these workspaces. Terminal invitations were filtered on the client, so + * accepted and revoked rows — and the addresses on them — left the server for no reason. + */ export async function listInvitationsForWorkspaces(workspaceIds: string[]) { if (workspaceIds.length === 0) return [] return db @@ -1895,5 +1899,10 @@ export async function listInvitationsForWorkspaces(workspaceIds: string[]) { }) .from(invitationWorkspaceGrant) .innerJoin(invitation, eq(invitation.id, invitationWorkspaceGrant.invitationId)) - .where(inArray(invitationWorkspaceGrant.workspaceId, workspaceIds)) + .where( + and( + inArray(invitationWorkspaceGrant.workspaceId, workspaceIds), + eq(invitation.status, 'pending') + ) + ) } diff --git a/apps/sim/lib/permission-groups/resolve.server.test.ts b/apps/sim/lib/permission-groups/resolve.server.test.ts index 786bd552044..0e313109396 100644 --- a/apps/sim/lib/permission-groups/resolve.server.test.ts +++ b/apps/sim/lib/permission-groups/resolve.server.test.ts @@ -5,13 +5,13 @@ import { resetEnvFlagsMock, setEnvFlags } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import type { DbOrTx } from '@/lib/db/types' -const { mockIsOrganizationOnEnterprisePlan, mockGetWorkspaceWithOwner } = vi.hoisted(() => ({ - mockIsOrganizationOnEnterprisePlan: vi.fn(), +const { mockIsOrganizationGovernanceActive, mockGetWorkspaceWithOwner } = vi.hoisted(() => ({ + mockIsOrganizationGovernanceActive: vi.fn(), mockGetWorkspaceWithOwner: vi.fn(), })) vi.mock('@/lib/billing/core/subscription', () => ({ - isOrganizationOnEnterprisePlan: mockIsOrganizationOnEnterprisePlan, + isOrganizationGovernanceActive: mockIsOrganizationGovernanceActive, })) vi.mock('@/lib/workspaces/permissions/utils', () => ({ @@ -37,12 +37,7 @@ const WORKSPACE_ID = 'workspace-1' * entitled" and these tests go red. */ function entitlementReadFails(): void { - mockIsOrganizationOnEnterprisePlan.mockImplementation( - async (_organizationId: string, onError?: string) => { - if (onError === 'throw') throw new Error('billing database unavailable') - return false - } - ) + mockIsOrganizationGovernanceActive.mockRejectedValue(new Error('billing database unavailable')) } describe('permission-group resolution under a failed entitlement read', () => { @@ -65,7 +60,7 @@ describe('permission-group resolution under a failed entitlement read', () => { await expect( resolveVerifiedUserAccessControlContext(USER_ID, WORKSPACE_ID, ORGANIZATION_ID) ).rejects.toThrow('billing database unavailable') - expect(mockIsOrganizationOnEnterprisePlan).toHaveBeenCalledWith(ORGANIZATION_ID, 'throw') + expect(mockIsOrganizationGovernanceActive).toHaveBeenCalledWith(ORGANIZATION_ID) }) it('rejects rather than resolving a null config from the workspace-lookup path', async () => { @@ -82,7 +77,7 @@ describe('permission-group resolution under a failed entitlement read', () => { await expect(getUserPermissionConfigForOrganization(ORGANIZATION_ID)).rejects.toThrow( 'billing database unavailable' ) - expect(mockIsOrganizationOnEnterprisePlan).toHaveBeenCalledWith(ORGANIZATION_ID, 'throw') + expect(mockIsOrganizationGovernanceActive).toHaveBeenCalledWith(ORGANIZATION_ID) }) /** @@ -91,7 +86,7 @@ describe('permission-group resolution under a failed entitlement read', () => { * inactive context. */ it('still resolves an inactive context when the organization is genuinely unentitled', async () => { - mockIsOrganizationOnEnterprisePlan.mockResolvedValue(false) + mockIsOrganizationGovernanceActive.mockResolvedValue(false) await expect( resolveVerifiedUserAccessControlContext(USER_ID, WORKSPACE_ID, ORGANIZATION_ID) @@ -106,17 +101,13 @@ describe('permission-group resolution under a failed entitlement read', () => { it('rechecks entitlement on the caller transaction after an unentitled preflight', async () => { const executor = {} as DbOrTx - mockIsOrganizationOnEnterprisePlan.mockResolvedValueOnce(false).mockResolvedValueOnce(true) + mockIsOrganizationGovernanceActive.mockResolvedValueOnce(false).mockResolvedValueOnce(true) await expect(isOrganizationPermissionRegimeActive(ORGANIZATION_ID)).resolves.toBe(false) await expect(isOrganizationPermissionRegimeActive(ORGANIZATION_ID, executor)).resolves.toBe( true ) - expect(mockIsOrganizationOnEnterprisePlan).toHaveBeenLastCalledWith( - ORGANIZATION_ID, - 'throw', - executor - ) + expect(mockIsOrganizationGovernanceActive).toHaveBeenLastCalledWith(ORGANIZATION_ID, executor) }) it('propagates a transaction entitlement read failure instead of disabling restrictions', async () => { @@ -126,10 +117,6 @@ describe('permission-group resolution under a failed entitlement read', () => { await expect(isOrganizationPermissionRegimeActive(ORGANIZATION_ID, executor)).rejects.toThrow( 'billing database unavailable' ) - expect(mockIsOrganizationOnEnterprisePlan).toHaveBeenCalledWith( - ORGANIZATION_ID, - 'throw', - executor - ) + expect(mockIsOrganizationGovernanceActive).toHaveBeenCalledWith(ORGANIZATION_ID, executor) }) }) diff --git a/apps/sim/lib/permission-groups/resolve.server.ts b/apps/sim/lib/permission-groups/resolve.server.ts index 0190a94b0ca..cc3ec9ef0f1 100644 --- a/apps/sim/lib/permission-groups/resolve.server.ts +++ b/apps/sim/lib/permission-groups/resolve.server.ts @@ -16,7 +16,7 @@ import { db } from '@sim/db' import { permissionGroup, permissionGroupMember, permissionGroupWorkspace } from '@sim/db/schema' import { and, asc, eq, sql } from 'drizzle-orm' -import { isOrganizationOnEnterprisePlan } from '@/lib/billing/core/subscription' +import { isOrganizationGovernanceActive } from '@/lib/billing/core/subscription' import { getAllowedIntegrationsFromEnv, isAccessControlEnabled, @@ -222,14 +222,13 @@ async function resolveUserAccessControlContextForOrganization( if (!organizationId) return inactiveUserAccessControlContext(null) /** - * `'throw'` because an unentitled organization resolves to `config: null`, - * and `null` is not a smaller permission set — it is *no* permission group at - * all: every capability allowed, every allowlist off. Under the lenient - * default a single subscription-read failure would be indistinguishable from - * a genuine plan lapse and would turn the whole regime off for the request. - * Throwing surfaces the outage as an error instead. + * The governance reader, not the feature gate: an unentitled organization resolves to + * `config: null`, and `null` is not a smaller permission set — it is *no* permission group at + * all: every capability allowed, every allowlist off. So neither a read failure nor a payment + * one may answer here; both would be indistinguishable from a genuine plan lapse and would lift + * the whole regime. It throws on the first and keeps governing through the second. */ - const isEnterprise = await isOrganizationOnEnterprisePlan(organizationId, 'throw') + const isEnterprise = await isOrganizationGovernanceActive(organizationId) if (!isEnterprise) { return inactiveUserAccessControlContext(organizationId) } @@ -316,7 +315,7 @@ export async function getUserPermissionConfigForOrganization( * part of the entitlement cache key, so this read cannot reuse a preflight * result. A permission-group lock alone only serializes group writes. * - * `'throw'` for the same reason as in + * Reads governance rather than feature entitlement, for the same reason as in * {@link resolveUserAccessControlContextForOrganization}. */ export async function isOrganizationPermissionRegimeActive( @@ -325,8 +324,8 @@ export async function isOrganizationPermissionRegimeActive( ): Promise { if (!isHosted && !isAccessControlEnabled) return false return executor - ? isOrganizationOnEnterprisePlan(organizationId, 'throw', executor) - : isOrganizationOnEnterprisePlan(organizationId, 'throw') + ? isOrganizationGovernanceActive(organizationId, executor) + : isOrganizationGovernanceActive(organizationId) } /** diff --git a/packages/auth/src/verify.ts b/packages/auth/src/verify.ts index b535da54df6..432d25d4cda 100644 --- a/packages/auth/src/verify.ts +++ b/packages/auth/src/verify.ts @@ -61,6 +61,10 @@ export function createVerifyAuth(options: VerifyAuthOptions): VerifyAuth { }), plugins: [ oneTimeToken({ + /** + * Unused by this instance, which only verifies: `/one-time-token/verify` reads the expiry + * off the row. The app sets its own far shorter window in apps/sim/lib/auth/auth.ts. + */ expiresIn: 24 * 60, }), ], From 000356fe245a9de62b081255861b5ba39d52a7e2 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 16 Sep 2026 13:10:52 -0700 Subject: [PATCH 17/43] feat(credential-groups): allowlist integrations by workspace (#7885) * feat(credential-groups): allowlist integrations by workspace * fix(credential-groups): refresh selectors and validate credential ownership --- .../workspace-access/route.ts | 2 +- .../settings/navigation.test.ts | 2 +- .../o/[organizationId]/settings/navigation.ts | 3 +- .../[workspaceId]/settings/navigation.test.ts | 2 +- .../settings-sidebar.test.tsx | 7 +- .../settings-sidebar/settings-sidebar.tsx | 3 +- apps/sim/components/settings/navigation.ts | 8 +- .../organization-account-providers.test.tsx | 45 +- .../organization-account-providers.tsx | 6 +- ...nization-account-workspace-access.test.tsx | 214 ++++--- .../organization-account-workspace-access.tsx | 185 +++--- .../organization-connected-accounts.tsx | 20 +- ...rganization-workspace-grant-modal.test.tsx | 219 +++++++ .../organization-workspace-grant-modal.tsx | 169 +++++ .../sim/ee/credential-groups/search-params.ts | 7 + .../queries/organization-accounts.test.tsx | 17 + .../hooks/queries/organization-accounts.ts | 30 +- .../api/contracts/organization-accounts.ts | 15 +- apps/sim/lib/copilot/vfs/workspace-vfs.ts | 20 +- .../application/authorization.test.ts | 23 +- .../application/authorization.ts | 27 +- .../application/list-credentials.test.ts | 35 +- .../application/list-credentials.ts | 39 +- .../application/list-mcp-connections.test.ts | 29 +- .../application/list-mcp-connections.ts | 20 +- .../application/organization-access.test.ts | 45 +- .../application/organization-access.ts | 40 +- .../organization-workspace-access.ts | 27 +- .../workspace-access-policy.test.ts | 138 ++++- .../application/workspace-access-policy.ts | 170 ++++-- .../workspace-organization-accounts.test.ts | 87 +++ .../workspace-organization-accounts.ts | 21 +- .../lib/credential-groups/credential-types.ts | 42 ++ .../lib/credential-groups/mcp-connections.ts | 9 + .../sim/lib/credential-groups/trigger.test.ts | 31 +- apps/sim/lib/credential-groups/trigger.ts | 18 +- .../lib/credential-groups/workspace-grants.ts | 42 ++ .../application/personal-connection.test.ts | 5 +- .../application/personal-credentials.test.ts | 22 + .../application/personal-credentials.ts | 9 +- .../resolve-personal-token.test.ts | 47 ++ .../application/resolve-personal-token.ts | 13 +- .../workspace-account-visibility.test.ts | 154 +++++ .../workspace-account-visibility.ts | 98 +++ apps/sim/lib/credentials/managed-mcp.ts | 15 +- apps/sim/lib/credentials/managed-oauth.ts | 7 + apps/sim/lib/credentials/personal-tokens.ts | 3 +- .../mcp/application/managed-auth-provider.ts | 15 +- .../application/managed-connections.test.ts | 7 +- .../mcp/application/managed-connections.ts | 13 +- .../conditions/credential-type.ts | 13 + .../resource-policies/conditions/registry.ts | 2 + .../lib/resource-policies/conditions/types.ts | 2 + apps/sim/lib/resource-policies/registry.ts | 1 + .../organization-section-access.test.ts | 7 +- .../organization-section-access.ts | 4 +- .../workspace-section-access.test.ts | 14 +- .../db/credential-group-resource-policies.ts | 103 ++-- ...credential_group_resource_policies.test.ts | 40 ++ ...check-tool-registry-boundary.baseline.json | 577 +++++++++--------- 60 files changed, 2322 insertions(+), 666 deletions(-) create mode 100644 apps/sim/ee/credential-groups/components/organization-workspace-grant-modal.test.tsx create mode 100644 apps/sim/ee/credential-groups/components/organization-workspace-grant-modal.tsx create mode 100644 apps/sim/ee/credential-groups/search-params.ts create mode 100644 apps/sim/lib/credential-groups/application/workspace-organization-accounts.test.ts create mode 100644 apps/sim/lib/credential-groups/credential-types.ts create mode 100644 apps/sim/lib/credential-groups/workspace-grants.ts create mode 100644 apps/sim/lib/credentials/application/workspace-account-visibility.test.ts create mode 100644 apps/sim/lib/credentials/application/workspace-account-visibility.ts create mode 100644 apps/sim/lib/resource-policies/conditions/credential-type.ts diff --git a/apps/sim/app/api/organizations/[id]/connected-accounts/workspace-access/route.ts b/apps/sim/app/api/organizations/[id]/connected-accounts/workspace-access/route.ts index 8ae3315a131..b63fce6598f 100644 --- a/apps/sim/app/api/organizations/[id]/connected-accounts/workspace-access/route.ts +++ b/apps/sim/app/api/organizations/[id]/connected-accounts/workspace-access/route.ts @@ -36,5 +36,5 @@ export const PUT = defineInternalJsonRoute({ errorPolicy: internalOrchestrationErrorPolicy, mapInput: ({ params, body }) => ({ organizationId: params.id, ...body }), useCase: updateOrganizationAccountWorkspaceAccess, - present: ({ revision, workspaceIds }) => ({ revision, workspaceIds }), + present: ({ revision, grants }) => ({ revision, grants }), }) diff --git a/apps/sim/app/o/[organizationId]/settings/navigation.test.ts b/apps/sim/app/o/[organizationId]/settings/navigation.test.ts index 94ec37b962a..bf92bfcb03f 100644 --- a/apps/sim/app/o/[organizationId]/settings/navigation.test.ts +++ b/apps/sim/app/o/[organizationId]/settings/navigation.test.ts @@ -33,7 +33,7 @@ describe('organization settings navigation', () => { it('uses Sources for administration when Search is available', () => { expect(organizationSettingsNavigation(true, enterprise, available)).toEqual( - ORGANIZATION_SETTINGS_ITEMS.filter(({ id }) => id !== 'connected-accounts') + ORGANIZATION_SETTINGS_ITEMS ) expect( organizationSettingsNavigation(true, enterprise, available).find( diff --git a/apps/sim/app/o/[organizationId]/settings/navigation.ts b/apps/sim/app/o/[organizationId]/settings/navigation.ts index 0a96e8a0754..f559e4ae62e 100644 --- a/apps/sim/app/o/[organizationId]/settings/navigation.ts +++ b/apps/sim/app/o/[organizationId]/settings/navigation.ts @@ -71,8 +71,7 @@ export function organizationSettingsNavigation( ) { return ORGANIZATION_SETTINGS_ITEMS.filter( (item) => - (item.id !== 'connected-accounts' || - (availability.connectedAccounts && !availability.search)) && + (item.id !== 'connected-accounts' || availability.connectedAccounts) && ((item.id !== 'search-mcp' && item.id !== 'search-slack' && item.id !== 'integrations') || availability.search) && resolveOrganizationSectionAccess({ diff --git a/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts index e1b26dc32a7..246a11b9cc9 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts @@ -33,7 +33,7 @@ describe('unified settings navigation', () => { { id: 'organization', label: 'Members', section: 'organization' }, { id: 'usage', label: 'Insights', section: 'organization' }, { id: 'secrets', label: 'Secrets', section: 'workspace' }, - { id: 'connected-accounts', label: 'Connected accounts', section: 'organization' }, + { id: 'connected-accounts', label: 'Credential Groups', section: 'organization' }, { id: 'custom-tools', label: 'Custom tools', section: 'workspace' }, { id: 'mcp', label: 'MCP tools', section: 'workspace' }, { id: 'apikeys', label: 'Sim API keys', section: 'workspace' }, diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.test.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.test.tsx index 7dbb6e5e041..93c385f93c1 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.test.tsx @@ -212,7 +212,7 @@ describe('workspace SettingsSidebar organization rollout', () => { expect(workspaceLink('billing')).toHaveTextContent('Subscription') expect(workspaceLink('usage')).toHaveTextContent('Insights') expect(workspaceLink('sso')).toHaveTextContent('Single sign-on') - expect(workspaceLink('connected-accounts')).toHaveTextContent('Connected accounts') + expect(workspaceLink('connected-accounts')).toHaveTextContent('Credential Groups') expect(container.querySelector('a[href^="/o/"]')).toBeNull() expectWorkspaceLinks() } @@ -240,7 +240,10 @@ describe('workspace SettingsSidebar organization rollout', () => { expect(links).toHaveLength(1) expect(links[0]).toHaveAttribute('href', '/o/host-org/settings/members') expect(links[0]).toHaveTextContent('Organization') - for (const section of ['organization', 'billing', 'usage', 'sso', 'connected-accounts']) { + if (role === 'admin') + expect(workspaceLink('connected-accounts')).toHaveTextContent('Credential Groups') + else expect(workspaceLink('connected-accounts')).toBeNull() + for (const section of ['organization', 'billing', 'usage', 'sso']) { expect(workspaceLink(section)).toBeNull() } expectWorkspaceLinks() diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx index 62e1e063a6f..bbe633e516a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx @@ -156,8 +156,7 @@ export function SettingsSidebar({ return Boolean( hostContext.hostOrganizationId && isOrgAdminOrOwner && - hostContext.features?.credentialGroups && - !hostContext.features?.organizationSearch + hostContext.features?.credentialGroups ) } if ( diff --git a/apps/sim/components/settings/navigation.ts b/apps/sim/components/settings/navigation.ts index e46615cf09f..abd3d5a4111 100644 --- a/apps/sim/components/settings/navigation.ts +++ b/apps/sim/components/settings/navigation.ts @@ -509,11 +509,11 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[] }, }, { - label: 'Connected accounts', + label: 'Credential Groups', icon: GridOffset, unified: { id: 'connected-accounts', - description: 'Manage accounts shared with your organization’s workflows.', + description: 'Manage integrations and workspace access for workflows and Chat.', group: 'organization', order: 1, organizationSection: 'connected-accounts', @@ -913,8 +913,8 @@ export const ORGANIZATION_SETTINGS_ITEMS: SettingsNavigationItem ( OrganizationAccountPeople: () => null, })) vi.mock('@/ee/credential-groups/components/organization-account-workspace-access', () => ({ - OrganizationAccountWorkspaceAccess: () => null, + OrganizationAccountWorkspaceAccess: () =>
Workspaces
, })) import { OrganizationAccountProviders } from '@/ee/credential-groups/components/organization-account-providers' @@ -167,6 +167,33 @@ describe('organization provider configuration UI', () => { }) } + it('keeps workspace allowlists in the Access tab and preserves the integration controls', async () => { + mocks.accounts.mockReturnValue({ + data: { + canManage: true, + credentialGroup: { ...group, options: [gmail] }, + availableProviders: ['gmail'], + }, + }) + await act(async () => + root.render( + + + + ) + ) + expect(container.textContent).toContain('Update configurations') + expect(container.querySelector('[data-testid="workspace-access"]')).toBeNull() + await clickButton('Access') + expect(container.querySelector('[data-testid="workspace-access"]')).not.toBeNull() + expect(container.textContent).not.toContain('Update configurations') + expect(container.querySelector('[role="combobox"]')).toBeNull() + await clickButton('Integrations') + expect(container.textContent).toContain('Update configurations') + expect(container.querySelector('[data-testid="workspace-access"]')).toBeNull() + expect(mocks.update).not.toHaveBeenCalled() + }) + it('shows only added providers and searches the remaining catalog', async () => { await render([], [gmail]) expect(container.textContent).toContain('Gmail') @@ -175,7 +202,7 @@ describe('organization provider configuration UI', () => { expect(container.textContent).not.toMatch(/Ready|Setup required/) expect(container.textContent).not.toContain('Fireflies') expect(container.querySelector('[role="radio"]')).toBeNull() - await clickButton('Add provider') + await clickButton('Add integration') expect(document.querySelector('[aria-label="Add Gmail"]')).toBeNull() const search = document.querySelector('[aria-label="Search providers"]') await act(async () => { @@ -191,7 +218,7 @@ describe('organization provider configuration UI', () => { it('adds Fireflies directly without an empty configuration modal', async () => { await render([]) - await clickButton('Add provider') + await clickButton('Add integration') await clickButton('Add Fireflies') expect(mocks.add).toHaveBeenCalledWith( { organizationId: 'org-1', connectorId: 'fireflies' }, @@ -208,7 +235,7 @@ describe('organization provider configuration UI', () => { it('adds Gmail directly without opening indexing configuration', async () => { await render([]) - await clickButton('Add provider') + await clickButton('Add integration') await clickButton('Add Gmail') expect(mocks.update).toHaveBeenCalledWith( { @@ -341,7 +368,7 @@ describe('organization provider configuration UI', () => { it('surfaces an add failure in the catalog and does not open configuration', async () => { mocks.add.mockImplementation(() => {}) await render([]) - await clickButton('Add provider') + await clickButton('Add integration') await clickButton('Add Fireflies') mocks.addError = new Error('Could not add Fireflies') await render([]) @@ -373,7 +400,7 @@ describe('organization provider configuration UI', () => { it('returns an unfinished Databricks entry to the catalog until its configuration is saved', async () => { await render([provider]) expect(container.textContent).not.toContain('Databricks') - await clickButton('Add provider') + await clickButton('Add integration') await clickButton('Add Databricks') expect(mocks.add).not.toHaveBeenCalled() expect(mocks.addAsync).not.toHaveBeenCalled() @@ -393,7 +420,7 @@ describe('organization provider configuration UI', () => { it('cancels Databricks setup without adding a provider', async () => { await render([]) - await clickButton('Add provider') + await clickButton('Add integration') await clickButton('Add Databricks') expect(mocks.setup).toHaveBeenCalledWith('org-1', false) expect(document.querySelector('[role="dialog"]')?.textContent).toContain('Add Databricks') @@ -413,7 +440,7 @@ describe('organization provider configuration UI', () => { it('adds Databricks with its complete configuration in one organization-scoped request', async () => { await render([]) - await clickButton('Add provider') + await clickButton('Add integration') await clickButton('Add Databricks') expect(mocks.addAsync).not.toHaveBeenCalled() await fill('Name', ' Analytics ') @@ -437,7 +464,7 @@ describe('organization provider configuration UI', () => { it('keeps Databricks configuration open after validation fails and allows correction', async () => { mocks.addAsync.mockRejectedValueOnce(new Error('Enter a valid Databricks MCP URL')) await render([]) - await clickButton('Add provider') + await clickButton('Add integration') await clickButton('Add Databricks') await fill('MCP URL', 'https://invalid.example.com/mcp') await fill('OAuth Client ID', 'client-1') diff --git a/apps/sim/ee/credential-groups/components/organization-account-providers.tsx b/apps/sim/ee/credential-groups/components/organization-account-providers.tsx index 60ecdd1c5dc..d303f264094 100644 --- a/apps/sim/ee/credential-groups/components/organization-account-providers.tsx +++ b/apps/sim/ee/credential-groups/components/organization-account-providers.tsx @@ -159,7 +159,7 @@ export function OrganizationAccountProviders({ return (
{options.length > 0 && ( @@ -177,7 +177,7 @@ export function OrganizationAccountProviders({ setCatalogOpen(true) }} > - Add provider + Add integration
} @@ -223,7 +223,7 @@ export function OrganizationAccountProviders({ ))} {!rows.length && ( - Add a provider to start connecting accounts. + Add an integration to start connecting accounts. )} diff --git a/apps/sim/ee/credential-groups/components/organization-account-workspace-access.test.tsx b/apps/sim/ee/credential-groups/components/organization-account-workspace-access.test.tsx index c8d8823f6c6..04e7cc23740 100644 --- a/apps/sim/ee/credential-groups/components/organization-account-workspace-access.test.tsx +++ b/apps/sim/ee/credential-groups/components/organization-account-workspace-access.test.tsx @@ -5,12 +5,11 @@ import { act, type ComponentProps, type ReactNode } from 'react' import { createRoot, type Root } from 'react-dom/client' import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import type { SettingsAction } from '@/components/settings/settings-header' import type { UpdateOrganizationAccountWorkspaceAccessBody, OrganizationAccountWorkspaceAccess as WorkspaceAccess, } from '@/lib/api/contracts/organization-accounts' -import type { CredentialGroupAddResourceModal } from '@/ee/credential-groups/components/credential-group-add-resource-modal' +import type { OrganizationWorkspaceGrantModal } from '@/ee/credential-groups/components/organization-workspace-grant-modal' const mocks = vi.hoisted(() => ({ useAccess: vi.fn(), @@ -18,7 +17,7 @@ const mocks = vi.hoisted(() => ({ reset: vi.fn(), mutationError: null as Error | null, isPending: false, - modal: null as ComponentProps | null, + grantModal: null as ComponentProps | null, toastError: vi.fn(), toastSuccess: vi.fn(), })) @@ -29,9 +28,10 @@ vi.mock('@sim/emcn', () => ({ {children} ), + ChipTag: ({ children }: { children: ReactNode }) => {children}, toast: { error: mocks.toastError, success: mocks.toastSuccess }, })) -vi.mock('@sim/emcn/icons', () => ({ Workspaces: () => null })) +vi.mock('@sim/emcn/icons', () => ({ Workspaces: () => null, Plus: () => null })) vi.mock('@/hooks/queries/organization-accounts', () => ({ useOrganizationAccountWorkspaceAccess: mocks.useAccess, useUpdateOrganizationAccountWorkspaceAccess: () => ({ @@ -41,29 +41,20 @@ vi.mock('@/hooks/queries/organization-accounts', () => ({ isPending: mocks.isPending, }), })) -vi.mock('@/app/workspace/[workspaceId]/settings/components/settings-panel', () => ({ - SettingsPanel: ({ - actions = [], - children, - }: { - actions?: SettingsAction[] - children: ReactNode - }) => ( -
- {actions.map((action) => ( - - ))} - {children} -
- ), -})) vi.mock('@/app/workspace/[workspaceId]/settings/components/settings-resource-row', () => ({ RESOURCE_LIST_STACK: '', - SettingsResourceRow: ({ title, trailing }: { title: string; trailing: ReactNode }) => ( + SettingsResourceRow: ({ + title, + trailing, + description, + }: { + title: string + trailing: ReactNode + description: ReactNode + }) => (
{title} + {description} {trailing}
), @@ -103,12 +94,12 @@ vi.mock('@/app/workspace/[workspaceId]/settings/components/row-actions-menu', () ), })) -vi.mock('@/ee/credential-groups/components/credential-group-add-resource-modal', () => ({ - CredentialGroupAddResourceModal: ( - props: ComponentProps +vi.mock('@/ee/credential-groups/components/organization-workspace-grant-modal', () => ({ + OrganizationWorkspaceGrantModal: ( + props: ComponentProps ) => { - mocks.modal = props - return
Add workspaces modal
+ mocks.grantModal = props + return
Manage workspace access modal
}, })) @@ -121,9 +112,24 @@ const WORKSPACES = [ ] const mountedRoots: Root[] = [] -function setAccess(workspaceIds = ['workspace-1'], revision = 3) { +function gmailGrants(workspaceIds: string[]): WorkspaceAccess['grants'] { + return workspaceIds.map((workspaceId) => ({ + workspaceId, + access: { mode: 'selected', credentialTypes: ['oauth:gmail'] }, + })) +} + +function setAccess(grants = gmailGrants(['workspace-1']), revision = 3) { mocks.useAccess.mockReturnValue({ - data: { workspaceIds, revision, workspaces: WORKSPACES } satisfies WorkspaceAccess, + data: { + grants, + revision, + workspaces: WORKSPACES, + credentialTypes: [ + { id: 'oauth:gmail', label: 'Gmail' }, + { id: 'oauth:google-calendar', label: 'Google Calendar' }, + ], + } satisfies WorkspaceAccess, error: null, }) } @@ -141,13 +147,9 @@ function renderAccess() { if (!match) throw new Error(`Button ${label} not found`) return match } - const add = async (ids: string[]) => { - act(() => button('Add workspaces').click()) - await act(async () => { - if (mocks.modal?.resourceType !== 'workspace') - throw new Error('Workspace picker is unavailable') - mocks.modal.onAdd(ids) - }) + const add = async (grant: WorkspaceAccess['grants'][number]) => { + act(() => button('Add workspace').click()) + await act(async () => mocks.grantModal?.onSave(grant)) } const rows = () => [...container.querySelectorAll('[data-workspace]')].map((row) => @@ -162,12 +164,12 @@ beforeEach(() => { vi.clearAllMocks() mocks.mutationError = null mocks.isPending = false - mocks.modal = null + mocks.grantModal = null setAccess() mocks.mutateAsync.mockImplementation( async (input: UpdateOrganizationAccountWorkspaceAccessBody) => { - setAccess(input.workspaceIds, input.revision + 1) - return { workspaceIds: input.workspaceIds, revision: input.revision + 1 } + setAccess(input.grants, input.revision + 1) + return { grants: input.grants, revision: input.revision + 1 } } ) }) @@ -178,94 +180,126 @@ afterEach(() => { }) }) -it('adds multiple workspaces immediately without Save or Discard actions', async () => { +it('adds a workspace with its chosen integrations and lists it with the existing grants', async () => { + setAccess([{ workspaceId: 'workspace-1', access: { mode: 'all' } }]) const editor = renderAccess() expect(editor.rows()).toEqual(['Finance']) - expect(editor.container.textContent).not.toMatch(/Save|Discard/) - - await editor.add(['workspace-3', 'workspace-2']) + expect(editor.container.textContent).toContain('All integrations') + const grant = { + workspaceId: 'workspace-2', + access: { mode: 'selected', credentialTypes: ['oauth:gmail', 'oauth:google-calendar'] }, + } satisfies WorkspaceAccess['grants'][number] + await editor.add(grant) editor.rerender() + if (mocks.grantModal?.mode !== 'create') throw new Error('Create modal not found') + expect(mocks.grantModal.workspaces).toEqual(WORKSPACES.slice(1)) + expect(mocks.mutateAsync).toHaveBeenCalledExactlyOnceWith({ + organizationId: 'org-1', + revision: 3, + grants: [{ workspaceId: 'workspace-1', access: { mode: 'all' } }, grant], + }) + expect(editor.rows()).toEqual(['Finance', 'Support']) + expect(editor.container.textContent).toContain('Gmail, Google Calendar') + expect(editor.container.textContent).not.toContain('Manage workspace access modal') +}) - expect(mocks.modal?.resources).toEqual(WORKSPACES.slice(1)) +it('adds an explicit All integrations grant', async () => { + const editor = renderAccess() + await editor.add({ workspaceId: 'workspace-2', access: { mode: 'all' } }) expect(mocks.mutateAsync).toHaveBeenCalledExactlyOnceWith({ organizationId: 'org-1', revision: 3, - workspaceIds: ['workspace-1', 'workspace-3', 'workspace-2'], + grants: [ + ...gmailGrants(['workspace-1']), + { workspaceId: 'workspace-2', access: { mode: 'all' } }, + ], }) - expect(editor.rows()).toEqual(['Finance', 'Support', 'Sales']) - expect(editor.container.textContent).not.toContain('Add workspaces modal') - expect(editor.container.textContent).not.toMatch(/Save|Discard/) }) -it('removes workspace access directly from the row action', async () => { - setAccess(['workspace-1', 'workspace-2']) +it('edits one workspace without changing other workspace grants', async () => { + setAccess(gmailGrants(['workspace-1', 'workspace-2'])) const editor = renderAccess() const finance = editor.container.querySelector('[data-workspace="Finance"]') if (!finance) throw new Error('Finance row not found') - await act(async () => editor.button('Remove', finance).click()) - editor.rerender() - + act(() => editor.button('Edit access', finance).click()) + if (mocks.grantModal?.mode !== 'edit') throw new Error('Edit modal not found') + expect(mocks.grantModal.grant).toEqual(gmailGrants(['workspace-1'])[0]) + const changed = { + workspaceId: 'workspace-1', + access: { mode: 'selected', credentialTypes: ['oauth:google-calendar'] }, + } satisfies WorkspaceAccess['grants'][number] + await act(async () => mocks.grantModal?.onSave(changed)) expect(mocks.mutateAsync).toHaveBeenCalledExactlyOnceWith({ organizationId: 'org-1', revision: 3, - workspaceIds: ['workspace-2'], + grants: [changed, ...gmailGrants(['workspace-2'])], }) - expect(editor.rows()).toEqual(['Support']) }) -it('does not change access when the add picker is cancelled', () => { +it('removes workspace access from the editor', async () => { const editor = renderAccess() - act(() => editor.button('Add workspaces').click()) - act(() => mocks.modal?.onClose()) + act(() => editor.button('Edit access').click()) + await act(async () => { + if (mocks.grantModal?.mode !== 'edit') throw new Error('Edit modal not found') + mocks.grantModal.onRemove() + }) + editor.rerender() + expect(mocks.mutateAsync).toHaveBeenCalledExactlyOnceWith({ + organizationId: 'org-1', + revision: 3, + grants: [], + }) + expect(editor.rows()).toEqual([]) + expect(editor.container.textContent).toContain('No workspaces have access') +}) +it('does not change access when adding a workspace is cancelled', () => { + const editor = renderAccess() + act(() => editor.button('Add workspace').click()) + act(() => mocks.grantModal?.onClose()) expect(mocks.mutateAsync).not.toHaveBeenCalled() expect(editor.rows()).toEqual(['Finance']) - expect(editor.container.textContent).not.toContain('Add workspaces modal') + expect(editor.container.textContent).not.toContain('Manage workspace access modal') }) -it('keeps the picker open and reports failed additions without changing the list', async () => { +it('keeps the editor open and reports failed additions without changing the list', async () => { const conflict = new Error('Workspace access changed while it was edited') mocks.mutateAsync.mockImplementation(async () => { mocks.mutationError = conflict throw conflict }) const editor = renderAccess() - await editor.add(['workspace-2', 'workspace-3']) + await editor.add(gmailGrants(['workspace-2'])[0]) editor.rerender() - expect(mocks.mutateAsync).toHaveBeenCalledOnce() expect(editor.rows()).toEqual(['Finance']) - expect(editor.container.textContent).toContain('Add workspaces modal') - expect(mocks.modal?.error).toBe(conflict.message) + expect(editor.container.textContent).toContain('Manage workspace access modal') + expect(mocks.grantModal?.error).toBe(conflict.message) expect(mocks.toastError).toHaveBeenCalledWith(conflict.message) expect(mocks.toastSuccess).not.toHaveBeenCalled() }) -it('disables access changes while a removal is in flight and preserves the row on failure', async () => { - let fail: ((error: Error) => void) | undefined - mocks.mutateAsync.mockImplementation(() => { - mocks.isPending = true - return new Promise((_, reject) => { - fail = reject - }) - }) - const editor = renderAccess() - act(() => editor.button('Remove').click()) - editor.rerender() - - expect(editor.button('Add workspaces').disabled).toBe(true) - expect(editor.rows()).toEqual(['Finance']) - expect(editor.container.textContent).not.toContain('Remove') +it.each(['create', 'edit'] as const)( + 'keeps the revision captured when the %s editor opened', + async (mode) => { + const editor = renderAccess() + act(() => editor.button(mode === 'create' ? 'Add workspace' : 'Edit access').click()) + setAccess(gmailGrants(['workspace-1']), 4) + editor.rerender() + await act(async () => + mocks.grantModal?.onSave(gmailGrants([mode === 'create' ? 'workspace-2' : 'workspace-1'])[0]) + ) + expect(mocks.mutateAsync).toHaveBeenCalledWith(expect.objectContaining({ revision: 3 })) + } +) - const error = new Error('Could not remove workspace access') - await act(async () => { - if (!fail) throw new Error('Request rejection is unavailable') - mocks.isPending = false - mocks.mutationError = error - fail(error) - }) +it('disables changes while saving and disables adding when all workspaces already have access', () => { + mocks.isPending = true + const editor = renderAccess() + expect(editor.button('Add workspace').disabled).toBe(true) + expect(editor.button('Edit access').disabled).toBe(true) + mocks.isPending = false + setAccess(gmailGrants(WORKSPACES.map((workspace) => workspace.id))) editor.rerender() - expect(editor.rows()).toEqual(['Finance']) - expect(editor.button('Remove').disabled).toBe(false) - expect(editor.container.textContent).toContain(error.message) + expect(editor.button('Add workspace').disabled).toBe(true) }) diff --git a/apps/sim/ee/credential-groups/components/organization-account-workspace-access.tsx b/apps/sim/ee/credential-groups/components/organization-account-workspace-access.tsx index ac14e9c7570..bc29477405e 100644 --- a/apps/sim/ee/credential-groups/components/organization-account-workspace-access.tsx +++ b/apps/sim/ee/credential-groups/components/organization-account-workspace-access.tsx @@ -2,27 +2,30 @@ import { useState } from 'react' import { Chip, toast } from '@sim/emcn' -import { Workspaces } from '@sim/emcn/icons' +import { Plus, Workspaces } from '@sim/emcn/icons' import { getErrorMessage } from '@sim/utils/errors' import type { OrganizationAccountWorkspaceAccess as WorkspaceAccess } from '@/lib/api/contracts/organization-accounts' import { ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT } from '@/lib/credential-groups/limits' -import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu' import { SettingsEmptyState, SettingsQueryErrorState, } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' -import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel' import { RESOURCE_LIST_STACK, SettingsResourceRow, } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row' import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' -import { CredentialGroupAddResourceModal } from '@/ee/credential-groups/components/credential-group-add-resource-modal' +import { OrganizationWorkspaceGrantModal } from '@/ee/credential-groups/components/organization-workspace-grant-modal' import { useOrganizationAccountWorkspaceAccess, useUpdateOrganizationAccountWorkspaceAccess, } from '@/hooks/queries/organization-accounts' +type Grant = WorkspaceAccess['grants'][number] +type GrantEditor = + | { mode: 'create'; revision: number } + | { mode: 'edit'; grant: Grant; revision: number } + interface OrganizationAccountWorkspaceAccessProps { organizationId: string } @@ -40,7 +43,8 @@ export function OrganizationAccountWorkspaceAccess({ onRetry={() => void access.refetch()} /> ) - if (!access.data) return null + if (!access.data) + return

Loading workspace access…

return ( [workspace.id, workspace])) - if (selected.size !== selectedIds.length) + const [editor, setEditor] = useState(null) + const byId = new Map(access.workspaces.map((workspace) => [workspace.id, workspace])) + const grantsById = new Map(access.grants.map((grant) => [grant.workspaceId, grant])) + const typesById = new Map(access.credentialTypes.map((type) => [type.id, type.label])) + if (grantsById.size !== access.grants.length) throw new Error('Workspace access contains duplicate workspaces') - for (const id of selectedIds) { - if (!workspacesById.has(id)) - throw new Error(`Workspace access references unavailable workspace ${id}`) + for (const grant of access.grants) { + if (!byId.has(grant.workspaceId)) + throw new Error(`Workspace access references unavailable workspace ${grant.workspaceId}`) + if (grant.access.mode === 'selected') { + for (const type of grant.access.credentialTypes) { + if (!typesById.has(type)) throw new Error(`Unknown credential type ${type}`) + } + } } - const allowedWorkspaces = access.workspaces.filter((workspace) => selected.has(workspace.id)) - const availableWorkspaces = access.workspaces.filter((workspace) => !selected.has(workspace.id)) + const allowedWorkspaces = access.workspaces.filter((workspace) => grantsById.has(workspace.id)) + const availableWorkspaces = access.workspaces.filter((workspace) => !grantsById.has(workspace.id)) - const updateAccess = async (workspaceIds: string[]) => { + const save = async (grants: WorkspaceAccess['grants'], revision: number) => { try { - await update.mutateAsync({ - organizationId, - revision: access.revision, - workspaceIds, - }) - setShowAddWorkspace(false) + await update.mutateAsync({ organizationId, revision, grants }) + setEditor(null) toast.success('Workspace access updated') } catch (error) { toast.error(getErrorMessage(error, 'Could not update workspace access')) } } + const saveGrant = (grant: Grant) => { + if (!editor) throw new Error('Workspace access editor is not open') + if (!byId.has(grant.workspaceId)) throw new Error('Selected workspace is unavailable') + if (editor.mode === 'create') { + if (grantsById.has(grant.workspaceId)) throw new Error('Workspace already has access') + if (access.grants.length >= ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT) + throw new Error( + `Workspace access cannot exceed ${ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT} workspaces` + ) + void save([...access.grants, grant], editor.revision) + } else { + if (grant.workspaceId !== editor.grant.workspaceId) + throw new Error('Cannot change the workspace of an existing grant') + void save( + access.grants.map((existing) => + existing.workspaceId === grant.workspaceId ? grant : existing + ), + editor.revision + ) + } + } return ( - + <> { - update.reset() - setShowAddWorkspace(true) - }} + leftAdornment={} disabled={ update.isPending || - availableWorkspaces.length === 0 || - selectedIds.length >= ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT + !availableWorkspaces.length || + access.grants.length >= ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT } + onClick={() => { + update.reset() + setEditor({ mode: 'create', revision: access.revision }) + }} > - Add workspaces + Add workspace } > {update.error && ( -

+

{update.error.message}

)} - {allowedWorkspaces.length === 0 ? ( + {!allowedWorkspaces.length ? ( No workspaces have access ) : (
- {allowedWorkspaces.map((workspace) => ( - } - iconFilled - title={workspace.name} - description='Authorized workflows can use every connected account in this organization' - disabled={update.isPending} - trailing={ - update.isPending ? undefined : ( - - void updateAccess(selectedIds.filter((id) => id !== workspace.id)), - }, - ]} - /> - ) - } - /> - ))} + {allowedWorkspaces.map((workspace) => { + const grant = grantsById.get(workspace.id)! + return ( + } + iconFilled + title={workspace.name} + description={ + grant.access.mode === 'all' + ? 'All integrations' + : grant.access.credentialTypes + .map((type) => typesById.get(type)!) + .sort((a, b) => a.localeCompare(b)) + .join(', ') + } + trailing={ + { + update.reset() + setEditor({ mode: 'edit', grant, revision: access.revision }) + }} + > + Edit access + + } + /> + ) + })}
)}
- {showAddWorkspace && ( - + void save( + access.grants.filter((grant) => grant.workspaceId !== editor.grant.workspaceId), + editor.revision + ), + } as const))} + credentialTypes={access.credentialTypes} disabled={update.isPending} error={update.error?.message} - onAdd={(ids) => { - for (const id of ids) { - if (!workspacesById.has(id)) throw new Error(`Workspace ${id} is unavailable`) - if (selected.has(id)) throw new Error(`Workspace ${id} already has access`) - } - if (selectedIds.length + ids.length > ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT) { - throw new Error( - `Workspace access cannot exceed ${ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT} workspaces` - ) - } - void updateAccess([...selectedIds, ...ids]) - }} - onClose={() => setShowAddWorkspace(false)} + onClose={() => setEditor(null)} + onSave={saveGrant} /> )} -
+ ) } diff --git a/apps/sim/ee/credential-groups/components/organization-connected-accounts.tsx b/apps/sim/ee/credential-groups/components/organization-connected-accounts.tsx index da3eef33092..bb85f8e25b9 100644 --- a/apps/sim/ee/credential-groups/components/organization-connected-accounts.tsx +++ b/apps/sim/ee/credential-groups/components/organization-connected-accounts.tsx @@ -1,17 +1,17 @@ 'use client' import { Chip, ChipSwitch } from '@sim/emcn' -import { parseAsStringLiteral, useQueryState } from 'nuqs' +import { useQueryStates } from 'nuqs' import { SettingsQueryErrorState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' import { OrganizationAccountPeople } from '@/ee/credential-groups/components/organization-account-people' import { OrganizationAccountProviders } from '@/ee/credential-groups/components/organization-account-providers' import { OrganizationAccountWorkspaceAccess } from '@/ee/credential-groups/components/organization-account-workspace-access' +import { credentialGroupsParsers } from '@/ee/credential-groups/search-params' import { useEnsureOrganizationAccounts, useOrganizationAccounts, } from '@/hooks/queries/organization-accounts' -const TABS = ['providers', 'people', 'workspace-access'] as const interface OrganizationConnectedAccountsProps { organizationId: string } @@ -21,13 +21,13 @@ export function OrganizationConnectedAccounts({ }: OrganizationConnectedAccountsProps) { const accounts = useOrganizationAccounts(organizationId) const ensure = useEnsureOrganizationAccounts() - const [tab, setTab] = useQueryState('tab', parseAsStringLiteral(TABS).withDefault('providers')) + const [{ tab }, setView] = useQueryStates(credentialGroupsParsers) const error = accounts.error ?? ensure.error if (error) return ( { ensure.reset() @@ -36,9 +36,9 @@ export function OrganizationConnectedAccounts({ /> ) if (!accounts.data) - return

Loading connected accounts…

+ return

Loading Credential Groups…

if (!accounts.data.canManage) - return

An organization admin manages connected accounts.

+ return

An organization admin manages Credential Groups.

const group = accounts.data.credentialGroup if (!group) return ( @@ -53,7 +53,7 @@ export function OrganizationConnectedAccounts({ disabled={ensure.isPending} onClick={() => ensure.mutate({ organizationId })} > - Set up connected accounts + Set up Credential Groups @@ -63,11 +63,11 @@ export function OrganizationConnectedAccounts({
void setTab(value)} + onChange={(value) => void setView({ tab: value })} options={[ - { value: 'providers', label: 'Providers' }, + { value: 'providers', label: 'Integrations' }, { value: 'people', label: 'People' }, - { value: 'workspace-access', label: 'Workspace access' }, + { value: 'workspace-access', label: 'Access' }, ]} />
diff --git a/apps/sim/ee/credential-groups/components/organization-workspace-grant-modal.test.tsx b/apps/sim/ee/credential-groups/components/organization-workspace-grant-modal.test.tsx new file mode 100644 index 00000000000..e2c01a12cb2 --- /dev/null +++ b/apps/sim/ee/credential-groups/components/organization-workspace-grant-modal.test.tsx @@ -0,0 +1,219 @@ +/** @vitest-environment jsdom */ +import { act } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { OrganizationAccountWorkspaceAccess } from '@/lib/api/contracts/organization-accounts' +import { OrganizationWorkspaceGrantModal } from '@/ee/credential-groups/components/organization-workspace-grant-modal' + +describe('workspace integration grant editor', () => { + let root: Root + let container: HTMLDivElement + const save = vi.fn() + const close = vi.fn() + const remove = vi.fn() + const credentialTypes = [ + { id: 'oauth:gmail', label: 'Gmail' }, + { id: 'oauth:google-calendar', label: 'Google Calendar' }, + ] satisfies OrganizationAccountWorkspaceAccess['credentialTypes'] + + beforeEach(() => { + vi.clearAllMocks() + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true) + container = document.createElement('div') + document.body.appendChild(container) + root = createRoot(container) + }) + afterEach(async () => { + await act(async () => root.unmount()) + container.remove() + vi.unstubAllGlobals() + }) + async function render( + access: OrganizationAccountWorkspaceAccess['grants'][number]['access'] | null = { mode: 'all' }, + disabled = false + ) { + await act(async () => + root.render( + + ) + ) + } + function button(label: string) { + const result = [...document.querySelectorAll('button')].find( + (element) => element.textContent === label + ) + if (!result) throw new Error(`Missing ${label} button`) + return result + } + function integrationOption(label: string) { + const result = [...document.querySelectorAll('[role="menuitem"]')].find( + (element) => element.textContent === label + ) + if (!result) throw new Error(`Missing ${label} option`) + return result + } + async function openIntegrations() { + const trigger = document.querySelector('[aria-label="Integrations"]') + expect(trigger).not.toBeNull() + await act(async () => + trigger?.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, button: 0 })) + ) + } + async function closeIntegrations() { + await act(async () => + document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })) + ) + } + async function click(label: string) { + await act(async () => button(label).click()) + } + async function selectWorkspace() { + const trigger = button('Select workspace') + await act(async () => + trigger.dispatchEvent(new MouseEvent('pointerdown', { bubbles: true, button: 0 })) + ) + const option = [...document.querySelectorAll('[role="menuitem"]')].find( + (element) => element.textContent === 'Finance' + ) + expect(option).toBeDefined() + await act(async () => option?.click()) + } + + it('creates a workspace grant only after selecting a workspace and integrations', async () => { + await render(null) + expect(button('Add workspace').disabled).toBe(true) + await selectWorkspace() + expect(button('Add workspace').disabled).toBe(true) + await openIntegrations() + expect(document.querySelector('[role="menuitem"]')?.textContent).toBe('All integrations') + await act(async () => integrationOption('Gmail').click()) + await closeIntegrations() + expect(button('Add workspace').disabled).toBe(false) + await click('Add workspace') + expect(save).toHaveBeenCalledExactlyOnceWith({ + workspaceId: 'finance', + access: { mode: 'selected', credentialTypes: ['oauth:gmail'] }, + }) + }) + + it('allows explicitly granting all current and future integrations', async () => { + await render(null) + await selectWorkspace() + await openIntegrations() + await act(async () => integrationOption('All integrations').click()) + await closeIntegrations() + expect(document.body.textContent).toContain('Includes integrations added in the future.') + await click('Add workspace') + expect(save).toHaveBeenCalledExactlyOnceWith({ + workspaceId: 'finance', + access: { mode: 'all' }, + }) + }) + + it('narrows broad access when a specific integration is selected', async () => { + await render() + await openIntegrations() + expect(document.querySelector('[role="menuitem"]')?.textContent).toBe('All integrations') + await act(async () => integrationOption('Gmail').click()) + await closeIntegrations() + await click('Save access') + expect(save).toHaveBeenCalledExactlyOnceWith({ + workspaceId: 'finance', + access: { mode: 'selected', credentialTypes: ['oauth:gmail'] }, + }) + }) + + it('does not implicitly grant future integrations when every individual integration is selected', async () => { + await render({ mode: 'selected', credentialTypes: ['oauth:gmail'] }) + await openIntegrations() + await act(async () => integrationOption('Google Calendar').click()) + await closeIntegrations() + await click('Save access') + expect(save).toHaveBeenCalledExactlyOnceWith({ + workspaceId: 'finance', + access: { mode: 'selected', credentialTypes: ['oauth:gmail', 'oauth:google-calendar'] }, + }) + }) + + it('replaces individual selections with an explicit all-integration grant', async () => { + await render({ mode: 'selected', credentialTypes: ['oauth:gmail'] }) + await openIntegrations() + await act(async () => integrationOption('All integrations').click()) + await closeIntegrations() + await click('Save access') + expect(save).toHaveBeenCalledExactlyOnceWith({ + workspaceId: 'finance', + access: { mode: 'all' }, + }) + }) + + it.each(['all', 'selected'] as const)( + 'does not treat clearing the last %s selection as unrestricted access', + async (mode) => { + await render(mode === 'all' ? { mode } : { mode, credentialTypes: ['oauth:gmail'] }) + await openIntegrations() + await act(async () => + integrationOption(mode === 'all' ? 'All integrations' : 'Gmail').click() + ) + await closeIntegrations() + expect(button('Save access').disabled).toBe(true) + expect(save).not.toHaveBeenCalled() + } + ) + + it('preserves saved selections while searching and cancels without saving', async () => { + await render({ mode: 'selected', credentialTypes: ['oauth:gmail'] }) + await openIntegrations() + const search = document.querySelector( + 'input[placeholder="Search integrations"]' + ) + await act(async () => { + Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set?.call( + search, + 'calendar' + ) + search?.dispatchEvent(new Event('input', { bubbles: true })) + }) + expect( + [...document.querySelectorAll('[role="menuitem"]')].map((item) => item.textContent) + ).toEqual(['Google Calendar']) + await act(async () => integrationOption('Google Calendar').click()) + await closeIntegrations() + await click('Save access') + expect(save).toHaveBeenLastCalledWith({ + workspaceId: 'finance', + access: { mode: 'selected', credentialTypes: ['oauth:gmail', 'oauth:google-calendar'] }, + }) + save.mockClear() + await click('Cancel') + expect(close).toHaveBeenCalledOnce() + expect(save).not.toHaveBeenCalled() + }) + + it('removes access explicitly and disables mutations while a request is pending', async () => { + await render() + await click('Remove access') + expect(remove).toHaveBeenCalledOnce() + expect(save).not.toHaveBeenCalled() + await render({ mode: 'all' }, true) + expect(button('Save access').disabled).toBe(true) + expect(button('Remove access').disabled).toBe(true) + expect(button('Cancel').disabled).toBe(true) + expect(document.querySelector('[aria-label="Integrations"]')?.disabled).toBe( + true + ) + }) +}) diff --git a/apps/sim/ee/credential-groups/components/organization-workspace-grant-modal.tsx b/apps/sim/ee/credential-groups/components/organization-workspace-grant-modal.tsx new file mode 100644 index 00000000000..fdc2eb01f69 --- /dev/null +++ b/apps/sim/ee/credential-groups/components/organization-workspace-grant-modal.tsx @@ -0,0 +1,169 @@ +'use client' + +import { useState } from 'react' +import { + ChipDropdown, + ChipModal, + ChipModalBody, + ChipModalError, + ChipModalField, + ChipModalFooter, + ChipModalHeader, + ChipSelect, +} from '@sim/emcn' +import type { OrganizationAccountWorkspaceAccess } from '@/lib/api/contracts/organization-accounts' +import { isOrganizationCredentialType } from '@/lib/credential-groups/credential-types' + +type Grant = OrganizationAccountWorkspaceAccess['grants'][number] +const ALL_INTEGRATIONS = 'all' + +interface OrganizationWorkspaceGrantModalBaseProps { + credentialTypes: OrganizationAccountWorkspaceAccess['credentialTypes'] + disabled: boolean + error?: string + onSave: (grant: Grant) => void + onClose: () => void +} + +interface CreateOrganizationWorkspaceGrantModalProps + extends OrganizationWorkspaceGrantModalBaseProps { + mode: 'create' + workspaces: OrganizationAccountWorkspaceAccess['workspaces'] +} + +interface EditOrganizationWorkspaceGrantModalProps + extends OrganizationWorkspaceGrantModalBaseProps { + mode: 'edit' + grant: Grant + workspaceName: string + onRemove: () => void +} + +type OrganizationWorkspaceGrantModalProps = + | CreateOrganizationWorkspaceGrantModalProps + | EditOrganizationWorkspaceGrantModalProps + +/** All integrations is an explicit grant; an empty picker selection never grants access. */ +export function OrganizationWorkspaceGrantModal(props: OrganizationWorkspaceGrantModalProps) { + const { credentialTypes, disabled, error, onSave, onClose } = props + const [workspaceId, setWorkspaceId] = useState( + props.mode === 'edit' ? props.grant.workspaceId : '' + ) + const [access, setAccess] = useState( + props.mode === 'edit' ? props.grant.access : { mode: 'selected', credentialTypes: [] } + ) + const title = props.mode === 'create' ? 'Add workspace' : `Edit ${props.workspaceName} access` + const save = () => { + if (!workspaceId) throw new Error('Select a workspace before granting access') + if ( + props.mode === 'create' && + !props.workspaces.some((workspace) => workspace.id === workspaceId) + ) + throw new Error('Selected workspace is unavailable') + if (access.mode === 'selected' && !access.credentialTypes.length) + throw new Error('Select at least one integration') + onSave({ workspaceId, access }) + } + + return ( + !open && !disabled && onClose()} + > + + {title} + + + {props.mode === 'create' && ( + + {(aria) => ( + ({ + value: workspace.id, + label: workspace.name, + }))} + value={workspaceId} + onChange={setWorkspaceId} + placeholder='Select workspace' + aria-label='Workspace' + searchable + searchPlaceholder='Search workspaces' + disabled={disabled} + fullWidth + dropdownWidth='trigger' + align='start' + {...aria} + /> + )} + + )} + + {(aria) => ( + ({ value: type.id, label: type.label })), + ]} + value={access.mode === 'all' ? [ALL_INTEGRATIONS] : access.credentialTypes} + onChange={(values) => { + if (access.mode !== 'all' && values.includes(ALL_INTEGRATIONS)) { + setAccess({ mode: 'all' }) + return + } + const selected = values.filter((value) => value !== ALL_INTEGRATIONS) + if (!selected.every(isOrganizationCredentialType)) + throw new Error('Unknown credential type') + setAccess({ mode: 'selected', credentialTypes: selected }) + }} + allLabel='Select integrations' + aria-label='Integrations' + showAllOption={false} + searchable + searchPlaceholder='Search integrations' + disabled={disabled} + fullWidth + matchTriggerWidth + align='start' + {...aria} + /> + )} + + {error} + + + + ) +} diff --git a/apps/sim/ee/credential-groups/search-params.ts b/apps/sim/ee/credential-groups/search-params.ts new file mode 100644 index 00000000000..c720460dca3 --- /dev/null +++ b/apps/sim/ee/credential-groups/search-params.ts @@ -0,0 +1,7 @@ +import { parseAsStringLiteral } from 'nuqs/server' + +export const credentialGroupsParsers = { + tab: parseAsStringLiteral(['providers', 'people', 'workspace-access'] as const).withDefault( + 'providers' + ), +} diff --git a/apps/sim/hooks/queries/organization-accounts.test.tsx b/apps/sim/hooks/queries/organization-accounts.test.tsx index 69f33d1f871..9752436158c 100644 --- a/apps/sim/hooks/queries/organization-accounts.test.tsx +++ b/apps/sim/hooks/queries/organization-accounts.test.tsx @@ -27,6 +27,7 @@ import { import { slackSearchKeys } from '@/hooks/queries/slack-search' import { knowledgeKeys } from '@/hooks/queries/utils/knowledge-keys' import { searchSourceKeys } from '@/hooks/queries/utils/search-source-keys' +import { selectorKeys, selectorQueryRoots } from '@/hooks/queries/utils/selector-keys' describe('personal account disconnect', () => { it.each([true, false])( @@ -163,6 +164,20 @@ describe('organization account setup updates', () => { const other = slackSearchKeys.manifest('org-2', 'Sim Search') const overview = searchSourceKeys.organizationOverview('org-1') const otherOverview = searchSourceKeys.organizationOverview('org-2') + const providerSelectors = [ + selectorKeys.scoped( + 'workspace.credentialGroupProviders', + { kind: 'workspace', workspaceId: 'workspace-1' }, + 'block-1' + ), + selectorKeys.scoped( + 'workspace.organizationMcpProviders', + { kind: 'workspace', workspaceId: 'workspace-1' }, + 'block-2' + ), + [...selectorQueryRoots.workflowSearchReplace, 'workflow-1'], + ] + for (const key of providerSelectors) client.setQueryData(key, { options: ['cached'] }) for (const key of [current, renamed, other]) client.setQueryData(key, { existingApp: 'A1' }) for (const key of [overview, otherOverview]) client.setQueryData(key, { providers: [] }) try { @@ -200,6 +215,8 @@ describe('organization account setup updates', () => { expect(client.getQueryState(other)?.isInvalidated).toBe(false) expect(client.getQueryState(overview)?.isInvalidated).toBe(success) expect(client.getQueryState(otherOverview)?.isInvalidated).toBe(false) + for (const key of providerSelectors) + expect(client.getQueryState(key)?.isInvalidated).toBe(success) } finally { await act(async () => root.unmount()) client.clear() diff --git a/apps/sim/hooks/queries/organization-accounts.ts b/apps/sim/hooks/queries/organization-accounts.ts index 733c3f03927..4efb5095aea 100644 --- a/apps/sim/hooks/queries/organization-accounts.ts +++ b/apps/sim/hooks/queries/organization-accounts.ts @@ -38,9 +38,12 @@ import { updateOrganizationAccountsContract, updateOrganizationAccountWorkspaceAccessContract, } from '@/lib/api/contracts/organization-accounts' +import { personalCredentialKeys } from '@/hooks/queries/personal-credentials' import { slackSearchKeys } from '@/hooks/queries/slack-search' +import { mcpKeys } from '@/hooks/queries/utils/mcp-keys' import { resetOrganizationSearchAccess } from '@/hooks/queries/utils/reset-organization-search-access' import { searchSourceKeys } from '@/hooks/queries/utils/search-source-keys' +import { invalidateSelectorQueries } from '@/hooks/queries/utils/selector-keys' export const ORGANIZATION_ACCOUNTS_STALE_TIME = 30_000 @@ -63,6 +66,9 @@ export function useDisconnectPersonalOrganizationAccount(organizationId: string) onSuccess: async () => { await Promise.all([ resetOrganizationSearchAccess(queryClient, organizationId), + queryClient.invalidateQueries({ queryKey: personalCredentialKeys.lists() }), + queryClient.invalidateQueries({ queryKey: mcpKeys.managedCatalog() }), + invalidateSelectorQueries(queryClient), queryClient.invalidateQueries({ queryKey: organizationAccountsKeys.detail(organizationId), }), @@ -145,6 +151,9 @@ export function useConfigureOrganizationMcp() { queryKey: organizationAccountsKeys.detail(organizationId), }), queryClient.invalidateQueries({ queryKey: organizationAccountsKeys.workspaces() }), + queryClient.invalidateQueries({ queryKey: personalCredentialKeys.lists() }), + queryClient.invalidateQueries({ queryKey: mcpKeys.managedCatalog() }), + invalidateSelectorQueries(queryClient), ]), }) } @@ -171,6 +180,9 @@ export function useUpdateOrganizationAccounts() { queryKey: organizationAccountsKeys.detail(organizationId), }), queryClient.invalidateQueries({ queryKey: organizationAccountsKeys.workspaces() }), + queryClient.invalidateQueries({ queryKey: personalCredentialKeys.lists() }), + queryClient.invalidateQueries({ queryKey: mcpKeys.managedCatalog() }), + invalidateSelectorQueries(queryClient), queryClient.invalidateQueries({ queryKey: slackSearchKeys.organizationManifests(organizationId), }), @@ -205,9 +217,13 @@ export function useWorkspaceOrganizationAccounts(workspaceId?: string, enabled = }, }) } -export function useOrganizationAccountWorkspaceAccess(organizationId: string) { +export function useOrganizationAccountWorkspaceAccess( + organizationId: string, + options?: { enabled?: boolean } +) { return useQuery({ queryKey: organizationAccountsKeys.access(organizationId), + enabled: Boolean(organizationId) && (options?.enabled ?? true), staleTime: ORGANIZATION_ACCOUNTS_STALE_TIME, queryFn: ({ signal }) => requestJson(getOrganizationAccountWorkspaceAccessContract, { @@ -233,6 +249,9 @@ export function useUpdateOrganizationAccountWorkspaceAccess() { queryKey: organizationAccountsKeys.access(organizationId), }), queryClient.invalidateQueries({ queryKey: organizationAccountsKeys.workspaces() }), + queryClient.invalidateQueries({ queryKey: personalCredentialKeys.lists() }), + queryClient.invalidateQueries({ queryKey: mcpKeys.managedCatalog() }), + invalidateSelectorQueries(queryClient), ]), }) } @@ -322,6 +341,9 @@ export function useRevokeOrganizationAccountEnrollment() { onSuccess: (_, { organizationId }) => Promise.all([ resetOrganizationSearchAccess(queryClient, organizationId), + queryClient.invalidateQueries({ queryKey: personalCredentialKeys.lists() }), + queryClient.invalidateQueries({ queryKey: mcpKeys.managedCatalog() }), + invalidateSelectorQueries(queryClient), queryClient.invalidateQueries({ queryKey: organizationAccountsKeys.detail(organizationId), }), @@ -345,6 +367,9 @@ export function useAddOrganizationAccountMcpProvider() { queryKey: organizationAccountsKeys.detail(organizationId), }), queryClient.invalidateQueries({ queryKey: organizationAccountsKeys.workspaces() }), + queryClient.invalidateQueries({ queryKey: personalCredentialKeys.lists() }), + queryClient.invalidateQueries({ queryKey: mcpKeys.managedCatalog() }), + invalidateSelectorQueries(queryClient), ]), }) } @@ -367,6 +392,9 @@ export function useRemoveOrganizationAccountMcpProvider() { queryKey: organizationAccountsKeys.detail(organizationId), }), queryClient.invalidateQueries({ queryKey: organizationAccountsKeys.workspaces() }), + queryClient.invalidateQueries({ queryKey: personalCredentialKeys.lists() }), + queryClient.invalidateQueries({ queryKey: mcpKeys.managedCatalog() }), + invalidateSelectorQueries(queryClient), ]), }) } diff --git a/apps/sim/lib/api/contracts/organization-accounts.ts b/apps/sim/lib/api/contracts/organization-accounts.ts index 7b08459e082..b38c3aaad1e 100644 --- a/apps/sim/lib/api/contracts/organization-accounts.ts +++ b/apps/sim/lib/api/contracts/organization-accounts.ts @@ -16,11 +16,16 @@ import { } from '@/lib/api/contracts/credential-groups' import { organizationIdSchema, workspaceIdSchema } from '@/lib/api/contracts/primitives' import { defineRouteContract } from '@/lib/api/contracts/types' +import { ORGANIZATION_CREDENTIAL_TYPES } from '@/lib/credential-groups/credential-types' import { ORGANIZATION_ACCOUNT_INDEXING_SOURCE_LIMIT, ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT, ORGANIZATION_VIEWER_ACCOUNT_LIMIT, } from '@/lib/credential-groups/limits' +import { + organizationAccountWorkspaceGrantsSchema, + organizationCredentialTypeSchema, +} from '@/lib/credential-groups/workspace-grants' const organizationAccountsParamsSchema = z.object({ id: organizationIdSchema }) const organizationCredentialGroupSchema = credentialGroupSchema.extend({ @@ -131,10 +136,7 @@ export type UpdateOrganizationAccountsBody = z.input new Set(ids).size === ids.length, 'Workspace IDs must be unique'), + grants: organizationAccountWorkspaceGrantsSchema, }) export const getOrganizationAccountWorkspaceAccessContract = defineRouteContract({ method: 'GET', @@ -143,6 +145,11 @@ export const getOrganizationAccountWorkspaceAccessContract = defineRouteContract response: { mode: 'json', schema: organizationAccountWorkspaceAccessSchema.extend({ + credentialTypes: z + .array( + z.object({ id: organizationCredentialTypeSchema, label: z.string().min(1).max(256) }) + ) + .max(ORGANIZATION_CREDENTIAL_TYPES.length), workspaces: z .array(z.object({ id: workspaceIdSchema, name: z.string().max(256) })) .max(ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT), diff --git a/apps/sim/lib/copilot/vfs/workspace-vfs.ts b/apps/sim/lib/copilot/vfs/workspace-vfs.ts index 7f5a55a89ed..03339aac020 100644 --- a/apps/sim/lib/copilot/vfs/workspace-vfs.ts +++ b/apps/sim/lib/copilot/vfs/workspace-vfs.ts @@ -18,6 +18,7 @@ import { and, desc, eq, inArray, isNotNull, isNull, or } from 'drizzle-orm' import { listApiKeys } from '@/lib/api-key/service' import { getAccountBillingSnapshot } from '@/lib/billing/core/account-billing-snapshot' import { hasWorkspaceSandboxAccess } from '@/lib/billing/core/subscription' +import { createCopilotChatPrincipal } from '@/lib/copilot/auth/application-delegation' import { buildWorkspaceContextMd, buildWorkspaceMd, @@ -116,10 +117,11 @@ import { } from '@/lib/core/config/env-flags' import { isPayloadSizeLimitError } from '@/lib/core/utils/stream-limits' import type { CredentialGroupRecord } from '@/lib/credential-groups/types' +import { CREDENTIAL_DELEGATION_AUDIENCE } from '@/lib/credentials/application/authorization' +import { listPersonalCredentials } from '@/lib/credentials/application/personal-credentials' import { getAccessibleEnvCredentials, getAccessibleOAuthCredentials, - getEnrolledManagedOAuthCredentials, } from '@/lib/credentials/environment' import { getPersonalAndWorkspaceEnv } from '@/lib/environment/utils' import { BINARY_DOC_TASKS, MAX_DOCUMENT_PREVIEW_CODE_BYTES } from '@/lib/execution/constants' @@ -3158,7 +3160,21 @@ export class WorkspaceVFS { getAccessibleOAuthCredentials(workspaceId, userId, { isWorkspaceAdmin }).then( async (accessible) => [ ...accessible, - ...(await getEnrolledManagedOAuthCredentials(workspaceId, userId)), + ...( + await listPersonalCredentials.execute({ + principal: createCopilotChatPrincipal( + { workspaceId, userId }, + CREDENTIAL_DELEGATION_AUDIENCE + ), + input: { workspaceId }, + }) + ).credentials + .filter((entry) => entry.type === 'managed_oauth') + .map((entry) => ({ + ...entry, + type: 'managed_oauth' as const, + role: 'member' as const, + })), ] ), listApiKeys(workspaceId), diff --git a/apps/sim/lib/credential-groups/application/authorization.test.ts b/apps/sim/lib/credential-groups/application/authorization.test.ts index 25b2d806c86..8638bed8885 100644 --- a/apps/sim/lib/credential-groups/application/authorization.test.ts +++ b/apps/sim/lib/credential-groups/application/authorization.test.ts @@ -48,6 +48,7 @@ const context = { organizationId: 'org-1', allowPersonalApiKeys: true, credentialId: 'credential-1', + credentialType: 'oauth:gmail' as const, credentialGroupId: 'group-1', credentialGroupEnrollmentId: 'enrollment-1', } @@ -69,7 +70,10 @@ function storedPolicy(workspaceIds: string[] = ['workspace-1']) { id: 'policy-1', organizationId: 'org-1', revision: 1, - document: buildOrganizationAccountAccessPolicy('group-1', workspaceIds), + document: buildOrganizationAccountAccessPolicy( + 'group-1', + workspaceIds.map((workspaceId) => ({ workspaceId, access: { mode: 'all' as const } })) + ), } } @@ -250,6 +254,23 @@ describe('requireCredentialGroupCredentialAccess', () => { ).rejects.toThrow('Reconnect this account') }) + it.each([executorPrincipal, copilotPrincipal])( + 'rechecks the canonical integration even when the workspace still has other grants', + async (makePrincipal) => { + await expect(requireAccess(makePrincipal())).resolves.toBeUndefined() + mocks.requirePolicy.mockResolvedValue({ + document: buildOrganizationAccountAccessPolicy('group-1', [ + { + workspaceId: 'workspace-1', + access: { mode: 'selected', credentialTypes: ['oauth:google-calendar'] }, + }, + ]), + }) + await expect(requireAccess(makePrincipal())).rejects.toMatchObject({ code: 'forbidden' }) + expect(mocks.requirePolicy).toHaveBeenCalledTimes(2) + } + ) + it('rechecks the org feature flag before credential use', async () => { mocks.isAvailable.mockResolvedValue(false) await expect(requireAccess(executorPrincipal())).rejects.toMatchObject({ code: 'not_found' }) diff --git a/apps/sim/lib/credential-groups/application/authorization.ts b/apps/sim/lib/credential-groups/application/authorization.ts index 13a51696fd6..2d6b92d3379 100644 --- a/apps/sim/lib/credential-groups/application/authorization.ts +++ b/apps/sim/lib/credential-groups/application/authorization.ts @@ -15,6 +15,10 @@ import { credentialGroupWorkflowAccessPolicyCodec, evaluateCredentialGroupActorCredentialAccess, } from '@/lib/credential-groups/application/workflow-access-policy' +import { + isOrganizationCredentialType, + type OrganizationCredentialType, +} from '@/lib/credential-groups/credential-types' import type { CredentialGroupCredentialListContext, ManagedCredentialGroupBinding, @@ -165,10 +169,19 @@ export async function requireCredentialGroupCredentialAccess( principal: Principal, context: CredentialGroupAuthorizationContext & { credentialId: string + credentialType: OrganizationCredentialType credentialGroupEnrollmentId: string }, resourcePolicy: ResourcePolicyBindingFor<'credential_group'> ): Promise { + if (principal.kind === 'delegated' && principal.serviceId === 'copilot') { + const subject = resolvePrincipalSubject(principal) + if (subject?.kind !== 'sim_user' || !subject.userId) { + throw new OrchestrationError('forbidden', 'Credential Group actor access required') + } + } else { + requireCredentialGroupWorkflowActor(principal) + } /** * A managed OAuth credential is usable only while its credential, enrollment, * option, and group are all live, whoever is using it: an admin disabling the @@ -179,21 +192,23 @@ export async function requireCredentialGroupCredentialAccess( if (binding && !isManagedCredentialGroupBindingLive(binding)) { throw new OrchestrationError('forbidden', 'Credential Group credential access denied') } + if (context.organizationId) { + if (!isOrganizationCredentialType(context.credentialType)) + throw new Error('Organization credential access requires a canonical credential type') + await requireOrganizationAccountsWorkspaceAccess( + { ...context, organizationId: context.organizationId }, + context.credentialType + ) + } if (principal.kind === 'delegated' && principal.serviceId === 'copilot') { return requireCredentialGroupActorCredentialAccess(principal, context, binding, resourcePolicy) } - requireCredentialGroupWorkflowActor(principal) - requireCurrentWorkflow(principal) if (!context.organizationId) { throw new OrchestrationError( 'forbidden', 'Reconnect this account in organization settings and replace the legacy Connected Accounts block' ) } - await requireOrganizationAccountsWorkspaceAccess({ - ...context, - organizationId: context.organizationId, - }) } export const credentialGroupDelegationPolicy = { diff --git a/apps/sim/lib/credential-groups/application/list-credentials.test.ts b/apps/sim/lib/credential-groups/application/list-credentials.test.ts index b940d915701..f0c73b24891 100644 --- a/apps/sim/lib/credential-groups/application/list-credentials.test.ts +++ b/apps/sim/lib/credential-groups/application/list-credentials.test.ts @@ -117,7 +117,10 @@ describe('listCredentialGroupCredentials', () => { beforeEach(() => { vi.clearAllMocks() mocks.requirePolicy.mockResolvedValue({ - document: buildOrganizationAccountAccessPolicy('group-1', ['workspace-1']), + document: buildOrganizationAccountAccessPolicy( + 'group-1', + ['workspace-1'].map((workspaceId) => ({ workspaceId, access: { mode: 'all' as const } })) + ), }) mocks.loadGroup.mockResolvedValue(groupContext) mocks.loadWorkspace.mockResolvedValue(workspaceContext) @@ -263,6 +266,36 @@ describe('listCredentialGroupCredentials', () => { }) }) + it('filters restricted integrations before pagination and rejects explicitly requesting them', async () => { + mocks.loadGroup.mockResolvedValue({ + ...groupContext, + options: [ + ...groupContext.options, + { ...groupContext.options[0], id: 'calendar-option', provider: 'google-calendar' }, + ], + }) + mocks.requirePolicy.mockResolvedValue({ + document: buildOrganizationAccountAccessPolicy('group-1', [ + { + workspaceId: 'workspace-1', + access: { mode: 'selected', credentialTypes: ['oauth:gmail'] }, + }, + ]), + }) + await listCredentialGroupCredentials.execute({ principal: executorPrincipal(), input }) + expect(mocks.listCredentials).toHaveBeenCalledWith( + expect.objectContaining({ credentialGroupOptionIds: ['option-1'], limit: 50 }) + ) + mocks.listCredentials.mockClear() + await expect( + listCredentialGroupCredentials.execute({ + principal: executorPrincipal(), + input: { ...input, credentialProviderIds: ['google-calendar'] }, + }) + ).rejects.toMatchObject({ code: 'forbidden' }) + expect(mocks.listCredentials).not.toHaveBeenCalled() + }) + it('filters by canonical providers active in the group', async () => { await listCredentialGroupCredentials.execute({ principal: executorPrincipal(), diff --git a/apps/sim/lib/credential-groups/application/list-credentials.ts b/apps/sim/lib/credential-groups/application/list-credentials.ts index a81af811f5c..203b43c63af 100644 --- a/apps/sim/lib/credential-groups/application/list-credentials.ts +++ b/apps/sim/lib/credential-groups/application/list-credentials.ts @@ -10,6 +10,7 @@ import { requireOrganizationAccountsWorkspaceAccess, resolveOrganizationAccountsWorkspaceContext, } from '@/lib/credential-groups/application/organization-workspace-access' +import { organizationAccountPolicyAllowsWorkspace } from '@/lib/credential-groups/application/workspace-access-policy' import { CredentialGroupCredentialCursorNotFoundError, type CredentialGroupCredentialReference, @@ -43,7 +44,7 @@ export const listCredentialGroupCredentials = defineAuthorizedWorkspaceUseCase({ authorizationOptions: { delegation: credentialGroupDelegationPolicy }, async authorizeResource({ principal, context }) { requireCredentialGroupWorkflowActor(principal) - await requireOrganizationAccountsWorkspaceAccess(context) + context.workspaceAccessPolicy = await requireOrganizationAccountsWorkspaceAccess(context) }, execute: async ({ input, context }): Promise => { if ( @@ -69,14 +70,17 @@ export const listCredentialGroupCredentials = defineAuthorizedWorkspaceUseCase({ if (credentialProviderIds.some((providerId) => !providerId.trim())) { throw new OrchestrationError('validation', 'Credential provider IDs must not be empty') } - const activeOptions = context.options.filter((option) => option.status === 'active') - const activeProviderIds = new Set( - activeOptions.map((option) => { - if (!isCredentialGroupProvider(option.provider)) { - throw new Error(`Credential Group provider is not registered: ${option.provider}`) - } - return getCredentialGroupProviderId(option.provider) + const policy = context.workspaceAccessPolicy + if (!policy) throw new Error('Credential listing requires workspace policy authorization') + const activeOptions = context.options + .filter((option) => option.status === 'active') + .map((option) => { + if (!isCredentialGroupProvider(option.provider)) + throw new Error(`Unsupported credential provider: ${option.provider}`) + return { ...option, provider: option.provider } }) + const activeProviderIds = new Set( + activeOptions.map((option) => getCredentialGroupProviderId(option.provider)) ) const invalidProviderIds = credentialProviderIds.filter( (providerId) => !activeProviderIds.has(providerId) @@ -88,12 +92,29 @@ export const listCredentialGroupCredentials = defineAuthorizedWorkspaceUseCase({ ) } + const allowedOptions = activeOptions.filter((option) => + organizationAccountPolicyAllowsWorkspace( + policy, + context.workspaceId, + `oauth:${option.provider}` + ) + ) + const allowedProviders = new Set( + allowedOptions.map((option) => getCredentialGroupProviderId(option.provider)) + ) + if (credentialProviderIds.some((providerId) => !allowedProviders.has(providerId))) { + throw new OrchestrationError( + 'forbidden', + 'This workspace is not allowed to use the requested credential provider' + ) + } + let page try { page = await listCredentialGroupCredentialReferences({ organizationId: context.organizationId, credentialGroupId: context.credentialGroupId, - credentialGroupOptionIds: activeOptions.map((option) => option.id), + credentialGroupOptionIds: allowedOptions.map((option) => option.id), limit: input.limit, cursor: input.cursor, email, diff --git a/apps/sim/lib/credential-groups/application/list-mcp-connections.test.ts b/apps/sim/lib/credential-groups/application/list-mcp-connections.test.ts index 25487113610..f16108c8d3b 100644 --- a/apps/sim/lib/credential-groups/application/list-mcp-connections.test.ts +++ b/apps/sim/lib/credential-groups/application/list-mcp-connections.test.ts @@ -97,7 +97,10 @@ describe('listCredentialGroupMcpConnections', () => { beforeEach(() => { vi.clearAllMocks() mocks.requirePolicy.mockResolvedValue({ - document: buildOrganizationAccountAccessPolicy('group-1', ['workspace-1']), + document: buildOrganizationAccountAccessPolicy( + 'group-1', + ['workspace-1'].map((workspaceId) => ({ workspaceId, access: { mode: 'all' as const } })) + ), }) mocks.loadGroup.mockResolvedValue(groupContext) mocks.loadWorkspace.mockResolvedValue(workspaceContext) @@ -142,6 +145,29 @@ describe('listCredentialGroupMcpConnections', () => { expect(mocks.listMcpConnections).not.toHaveBeenCalled() }) + it('limits discovery to allowed MCP types and rejects an explicit restricted connector', async () => { + mocks.requirePolicy.mockResolvedValue({ + document: buildOrganizationAccountAccessPolicy('group-1', [ + { + workspaceId: 'workspace-1', + access: { mode: 'selected', credentialTypes: ['mcp:fireflies'] }, + }, + ]), + }) + await listCredentialGroupMcpConnections.execute({ principal: executorPrincipal(), input }) + expect(mocks.listMcpConnections).toHaveBeenCalledWith( + expect.objectContaining({ allowedConnectorIds: ['fireflies'] }) + ) + mocks.listMcpConnections.mockClear() + await expect( + listCredentialGroupMcpConnections.execute({ + principal: executorPrincipal(), + input: { ...input, connectorId: 'granola' }, + }) + ).rejects.toMatchObject({ code: 'forbidden' }) + expect(mocks.listMcpConnections).not.toHaveBeenCalled() + }) + it('lists bounded MCP connection references after authorization and entitlement checks', async () => { const result = await listCredentialGroupMcpConnections.execute({ principal: executorPrincipal(), @@ -160,6 +186,7 @@ describe('listCredentialGroupMcpConnections', () => { email: 'person@example.com', mcpServerId: 'mcp-server-1', connectorId: undefined, + allowedConnectorIds: ['fireflies', 'granola', 'databricks'], }) expect(result).toEqual({ mcpConnections: [ diff --git a/apps/sim/lib/credential-groups/application/list-mcp-connections.ts b/apps/sim/lib/credential-groups/application/list-mcp-connections.ts index 576b86d27dd..d0baadc58a2 100644 --- a/apps/sim/lib/credential-groups/application/list-mcp-connections.ts +++ b/apps/sim/lib/credential-groups/application/list-mcp-connections.ts @@ -10,7 +10,11 @@ import { requireOrganizationAccountsWorkspaceAccess, resolveOrganizationAccountsWorkspaceContext, } from '@/lib/credential-groups/application/organization-workspace-access' -import { getManagedMcpConnector } from '@/lib/credential-groups/managed-mcp-connectors' +import { organizationAccountPolicyAllowsWorkspace } from '@/lib/credential-groups/application/workspace-access-policy' +import { + getManagedMcpConnector, + MANAGED_MCP_CONNECTOR_IDS, +} from '@/lib/credential-groups/managed-mcp-connectors' import { CredentialGroupMcpConnectionCursorNotFoundError, type CredentialGroupMcpConnectionReference, @@ -41,7 +45,7 @@ export const listCredentialGroupMcpConnections = defineAuthorizedWorkspaceUseCas authorizationOptions: { delegation: credentialGroupDelegationPolicy }, async authorizeResource({ principal, context }) { requireCredentialGroupWorkflowActor(principal) - await requireOrganizationAccountsWorkspaceAccess(context) + context.workspaceAccessPolicy = await requireOrganizationAccountsWorkspaceAccess(context) }, execute: async ({ input, context }): Promise => { if ( @@ -68,6 +72,17 @@ export const listCredentialGroupMcpConnections = defineAuthorizedWorkspaceUseCas throw new OrchestrationError('validation', 'MCP server ID must not be empty') } + const policy = context.workspaceAccessPolicy + if (!policy) throw new Error('MCP listing requires workspace policy authorization') + const allowedConnectorIds = MANAGED_MCP_CONNECTOR_IDS.filter((id) => + organizationAccountPolicyAllowsWorkspace(policy, context.workspaceId, `mcp:${id}`) + ) + if (input.connectorId && !allowedConnectorIds.some((id) => id === input.connectorId)) { + throw new OrchestrationError( + 'forbidden', + 'This workspace is not allowed to use the requested MCP provider' + ) + } let page try { page = await listCredentialGroupMcpConnectionReferences({ @@ -78,6 +93,7 @@ export const listCredentialGroupMcpConnections = defineAuthorizedWorkspaceUseCas email, mcpServerId, connectorId: input.connectorId, + allowedConnectorIds, }) } catch (error) { if (error instanceof CredentialGroupMcpConnectionCursorNotFoundError) { diff --git a/apps/sim/lib/credential-groups/application/organization-access.test.ts b/apps/sim/lib/credential-groups/application/organization-access.test.ts index 32c4897be29..020657e082c 100644 --- a/apps/sim/lib/credential-groups/application/organization-access.test.ts +++ b/apps/sim/lib/credential-groups/application/organization-access.test.ts @@ -55,6 +55,7 @@ import { startOrganizationAccountConnection, } from '@/lib/credential-groups/application/organization-accounts' import { buildOrganizationAccountAccessPolicy } from '@/lib/credential-groups/application/workspace-access-policy' +import { ORGANIZATION_CREDENTIAL_TYPES } from '@/lib/credential-groups/credential-types' import { ResourcePolicyRevisionConflictError } from '@/lib/resource-policies/repository' const principal: SessionPrincipal = { @@ -62,7 +63,16 @@ const principal: SessionPrincipal = { userId: 'admin-user', sessionId: 'session-1', } -const input = { organizationId: 'org-1', revision: 3, workspaceIds: ['workspace-1'] } +const input = { + organizationId: 'org-1', + revision: 3, + grants: [ + { + workspaceId: 'workspace-1', + access: { mode: 'selected' as const, credentialTypes: ['oauth:gmail' as const] }, + }, + ], +} describe('organization workspace sharing administration', () => { beforeEach(() => { @@ -166,7 +176,15 @@ describe('organization workspace sharing administration', () => { queueTableRows(schemaMock.workspace, [{ id: 'workspace-1' }]) await expect( updateOrganizationAccountWorkspaceAccess.execute({ principal, input }) - ).resolves.toMatchObject({ revision: 4, workspaceIds: ['workspace-1'] }) + ).resolves.toMatchObject({ + revision: 4, + grants: [ + { + workspaceId: 'workspace-1', + access: { mode: 'selected' as const, credentialTypes: ['oauth:gmail' as const] }, + }, + ], + }) expect(eq).toHaveBeenCalledWith(schemaMock.member.userId, 'admin-user') expect(eq).toHaveBeenCalledWith(schemaMock.member.organizationId, 'org-1') expect(eq).toHaveBeenCalledWith(schemaMock.workspace.organizationId, 'org-1') @@ -175,6 +193,7 @@ describe('organization workspace sharing administration', () => { organizationId: 'org-1', actorUserId: 'admin-user', expectedRevision: 3, + document: buildOrganizationAccountAccessPolicy('group-1', input.grants), }) ) }) @@ -193,21 +212,37 @@ describe('organization workspace sharing administration', () => { await expect( updateOrganizationAccountWorkspaceAccess.execute({ principal, - input: { ...input, workspaceIds: [] }, + input: { ...input, grants: [] }, }) - ).resolves.toMatchObject({ workspaceIds: [] }) + ).resolves.toMatchObject({ grants: [] }) expect(mocks.write).toHaveBeenCalledWith( expect.objectContaining({ document: buildOrganizationAccountAccessPolicy('group-1', []) }) ) }) + it('rejects selected grants exceeding the persisted policy size bound before writing', async () => { + queueTableRows(schemaMock.member, [{ role: 'admin' }]) + const grants = Array.from({ length: 1000 }, (_, index) => ({ + workspaceId: `workspace-${index}`, + access: { mode: 'selected' as const, credentialTypes: [...ORGANIZATION_CREDENTIAL_TYPES] }, + })) + queueTableRows( + schemaMock.workspace, + grants.map((grant) => ({ id: grant.workspaceId })) + ) + await expect( + updateOrganizationAccountWorkspaceAccess.execute({ principal, input: { ...input, grants } }) + ).rejects.toMatchObject({ code: 'validation', message: expect.stringContaining('too large') }) + expect(mocks.write).not.toHaveBeenCalled() + }) + it('rejects a stale revision rather than overwriting another admin', async () => { queueTableRows(schemaMock.member, [{ role: 'admin' }]) mocks.write.mockRejectedValue(new ResourcePolicyRevisionConflictError()) await expect( updateOrganizationAccountWorkspaceAccess.execute({ principal, - input: { ...input, workspaceIds: [] }, + input: { ...input, grants: [] }, }) ).rejects.toMatchObject({ code: 'conflict' }) }) diff --git a/apps/sim/lib/credential-groups/application/organization-access.ts b/apps/sim/lib/credential-groups/application/organization-access.ts index 7300e2bbfb8..1c73f3a6cd6 100644 --- a/apps/sim/lib/credential-groups/application/organization-access.ts +++ b/apps/sim/lib/credential-groups/application/organization-access.ts @@ -1,4 +1,5 @@ import { db } from '@sim/db' +import { ORGANIZATION_ACCOUNT_POLICY_DOCUMENT_MAX_BYTES } from '@sim/db/credential-group-resource-policies' import { workspace } from '@sim/db/schema' import { and, asc, eq, inArray, isNull } from 'drizzle-orm' import type { OrganizationMembershipContext } from '@/lib/core/application/organization-authorization' @@ -7,12 +8,16 @@ import { OrchestrationError } from '@/lib/core/orchestration/types' import { defineOrganizationAccountsUseCase } from '@/lib/credential-groups/application/organization-accounts' import { buildOrganizationAccountAccessPolicy, - listOrganizationAccountWorkspaceIds, + listOrganizationAccountWorkspaceGrants, organizationAccountAccessPolicyCodec, - organizationAccountWorkspaceIdsSchema, } from '@/lib/credential-groups/application/workspace-access-policy' +import { getOrganizationCredentialTypeCatalog } from '@/lib/credential-groups/credential-types' import { loadScopedAccountsCredentialListContext } from '@/lib/credential-groups/credentials' import { ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT } from '@/lib/credential-groups/limits' +import { + type OrganizationAccountWorkspaceGrant, + organizationAccountWorkspaceGrantsSchema, +} from '@/lib/credential-groups/workspace-grants' import { ResourcePolicyRevisionConflictError, requireResourcePolicy, @@ -71,8 +76,9 @@ export const getOrganizationAccountWorkspaceAccess = defineOrganizationAccountsU ) return { revision: policy.revision, - workspaceIds: listOrganizationAccountWorkspaceIds(policy.document), + grants: listOrganizationAccountWorkspaceGrants(policy.document), workspaces, + credentialTypes: getOrganizationCredentialTypeCatalog(), } }, }) @@ -83,14 +89,14 @@ export const updateOrganizationAccountWorkspaceAccess = defineOrganizationAccoun input, context, }: { - input: { organizationId: string; revision: number; workspaceIds: string[] } + input: { organizationId: string; revision: number; grants: OrganizationAccountWorkspaceGrant[] } context: OrganizationMembershipContext }) { - const parsed = organizationAccountWorkspaceIdsSchema.safeParse(input.workspaceIds) + const parsed = organizationAccountWorkspaceGrantsSchema.safeParse(input.grants) if (!parsed.success) throw new OrchestrationError( 'validation', - 'Workspace IDs must be unique, valid identifiers within the supported limit' + 'Workspace grants must contain unique workspace IDs and valid integration selections' ) const group = await requireGroup(context.organizationId) if (parsed.data.length) { @@ -100,7 +106,10 @@ export const updateOrganizationAccountWorkspaceAccess = defineOrganizationAccoun .where( and( eq(workspace.organizationId, context.organizationId), - inArray(workspace.id, parsed.data), + inArray( + workspace.id, + parsed.data.map((grant) => grant.workspaceId) + ), isNull(workspace.archivedAt) ) ) @@ -110,6 +119,17 @@ export const updateOrganizationAccountWorkspaceAccess = defineOrganizationAccoun 'Every allowed workspace must be active and belong to this organization' ) } + const document = buildOrganizationAccountAccessPolicy(group.credentialGroupId, parsed.data) + /** Indented JSON conservatively includes the whitespace PostgreSQL adds to jsonb text. */ + if ( + Buffer.byteLength(JSON.stringify(document, null, 1), 'utf8') > + ORGANIZATION_ACCOUNT_POLICY_DOCUMENT_MAX_BYTES + ) { + throw new OrchestrationError( + 'validation', + 'Workspace access policy is too large. Reduce the number of selected integrations or workspaces.' + ) + } try { const policy = await writeResourcePolicy({ organizationId: context.organizationId, @@ -117,14 +137,14 @@ export const updateOrganizationAccountWorkspaceAccess = defineOrganizationAccoun resourceId: group.credentialGroupId, codec: organizationAccountAccessPolicyCodec, expectedRevision: input.revision, - document: buildOrganizationAccountAccessPolicy(group.credentialGroupId, parsed.data), + document, actorUserId: context.userId, }) return { credentialGroupId: group.credentialGroupId, name: group.name, revision: policy.revision, - workspaceIds: listOrganizationAccountWorkspaceIds(policy.document), + grants: listOrganizationAccountWorkspaceGrants(policy.document), } } catch (error) { if (error instanceof ResourcePolicyRevisionConflictError) @@ -138,6 +158,6 @@ export const updateOrganizationAccountWorkspaceAccess = defineOrganizationAccoun projectAudit: (result) => ({ resourceId: result.credentialGroupId, resourceName: result.name, - description: `Allowed ${result.workspaceIds.length} workspaces to use organization connected accounts`, + description: `Allowed ${result.grants.length} workspaces to use organization connected accounts`, }), }) diff --git a/apps/sim/lib/credential-groups/application/organization-workspace-access.ts b/apps/sim/lib/credential-groups/application/organization-workspace-access.ts index 76ed97340cf..b6f09a114ac 100644 --- a/apps/sim/lib/credential-groups/application/organization-workspace-access.ts +++ b/apps/sim/lib/credential-groups/application/organization-workspace-access.ts @@ -1,16 +1,19 @@ import { OrchestrationError } from '@/lib/core/orchestration/types' import type { CredentialGroupApplicationContext } from '@/lib/credential-groups/application/authorization' import { resolveCredentialGroupWorkspaceContext } from '@/lib/credential-groups/application/context' +import type { OrganizationAccountAccessPolicy } from '@/lib/credential-groups/application/workspace-access-policy' import { organizationAccountAccessPolicyCodec, organizationAccountPolicyAllowsWorkspace, } from '@/lib/credential-groups/application/workspace-access-policy' +import type { OrganizationCredentialType } from '@/lib/credential-groups/credential-types' import { loadScopedAccountsCredentialListContext } from '@/lib/credential-groups/credentials' import { isScopedCredentialGroupsAvailable } from '@/lib/credential-groups/scoped-availability' import { requireResourcePolicy } from '@/lib/resource-policies/repository' export interface OrganizationAccountsWorkspaceContext extends CredentialGroupApplicationContext { organizationId: string + workspaceAccessPolicy?: OrganizationAccountAccessPolicy } /** Resolves the singleton using the executing workspace's current organization. */ @@ -31,12 +34,15 @@ export async function resolveOrganizationAccountsWorkspaceContext( } /** Uses live policy and ownership; cached selections and deployment snapshots never grant access. */ -export async function requireOrganizationAccountsWorkspaceAccess(context: { - workspaceId: string - workspaceOrganizationId: string | null - organizationId: string - credentialGroupId: string -}): Promise { +export async function requireOrganizationAccountsWorkspaceAccess( + context: { + workspaceId: string + workspaceOrganizationId: string | null + organizationId: string + credentialGroupId: string + }, + credentialType?: OrganizationCredentialType +): Promise { if (context.organizationId !== context.workspaceOrganizationId) { throw new OrchestrationError('forbidden', 'Connected accounts belong to another organization') } @@ -54,10 +60,15 @@ export async function requireOrganizationAccountsWorkspaceAccess(context: { resourceId: context.credentialGroupId, codec: organizationAccountAccessPolicyCodec, }) - if (!organizationAccountPolicyAllowsWorkspace(policy.document, context.workspaceId)) { + if ( + !organizationAccountPolicyAllowsWorkspace(policy.document, context.workspaceId, credentialType) + ) { throw new OrchestrationError( 'forbidden', - 'An organization admin must allow this workspace to use connected accounts' + credentialType + ? `This workspace is not allowed to use ${credentialType} credentials` + : 'An organization admin must allow this workspace to use Credential Groups' ) } + return policy.document } diff --git a/apps/sim/lib/credential-groups/application/workspace-access-policy.test.ts b/apps/sim/lib/credential-groups/application/workspace-access-policy.test.ts index c29a825eab1..5e37acc8c6d 100644 --- a/apps/sim/lib/credential-groups/application/workspace-access-policy.test.ts +++ b/apps/sim/lib/credential-groups/application/workspace-access-policy.test.ts @@ -2,10 +2,12 @@ import { describe, expect, it } from 'vitest' import { buildOrganizationAccountAccessPolicy, + listOrganizationAccountWorkspaceGrants, listOrganizationAccountWorkspaceIds, organizationAccountAccessPolicyCodec, organizationAccountPolicyAllowsWorkspace, } from '@/lib/credential-groups/application/workspace-access-policy' +import { organizationAccountWorkspaceGrantsSchema } from '@/lib/credential-groups/workspace-grants' describe('organization account workspace policy', () => { it('denies every workspace by default', () => { @@ -14,7 +16,13 @@ describe('organization account workspace policy', () => { }) it('grants only selected workspaces without a workflow or deployment condition', () => { - const policy = buildOrganizationAccountAccessPolicy('group-1', ['workspace-2', 'workspace-1']) + const policy = buildOrganizationAccountAccessPolicy( + 'group-1', + ['workspace-2', 'workspace-1'].map((workspaceId) => ({ + workspaceId, + access: { mode: 'all' as const }, + })) + ) expect(listOrganizationAccountWorkspaceIds(policy)).toEqual(['workspace-1', 'workspace-2']) expect(organizationAccountPolicyAllowsWorkspace(policy, 'workspace-1')).toBe(true) expect(organizationAccountPolicyAllowsWorkspace(policy, 'workspace-3')).toBe(false) @@ -35,7 +43,10 @@ describe('organization account workspace policy', () => { { type: 'workflow', workflowId: 'workflow-1' }, { type: 'knowledge_connector', connectorId: 'connector-1' }, ]) { - const policy = buildOrganizationAccountAccessPolicy('group-1', ['workspace-1']) + const policy = buildOrganizationAccountAccessPolicy( + 'group-1', + ['workspace-1'].map((workspaceId) => ({ workspaceId, access: { mode: 'all' as const } })) + ) expect(() => organizationAccountAccessPolicyCodec.parse( { ...policy, statements: [{ ...policy.statements[0], principals: [principal] }] }, @@ -47,8 +58,127 @@ describe('organization account workspace policy', () => { it('rejects duplicate selections and malformed IDs', () => { expect(() => - buildOrganizationAccountAccessPolicy('group-1', ['workspace-1', 'workspace-1']) + buildOrganizationAccountAccessPolicy( + 'group-1', + ['workspace-1', 'workspace-1'].map((workspaceId) => ({ + workspaceId, + access: { mode: 'all' as const }, + })) + ) + ).toThrow() + expect(() => + buildOrganizationAccountAccessPolicy( + 'group-1', + [' workspace-1 '].map((workspaceId) => ({ workspaceId, access: { mode: 'all' as const } })) + ) ).toThrow() - expect(() => buildOrganizationAccountAccessPolicy('group-1', [' workspace-1 '])).toThrow() + }) +}) + +describe('integration-specific organization grants', () => { + const grants = [ + { + workspaceId: 'mail-workspace', + access: { + mode: 'selected' as const, + credentialTypes: ['oauth:gmail' as const, 'mcp:fireflies' as const], + }, + }, + { + workspaceId: 'calendar-workspace', + access: { + mode: 'selected' as const, + credentialTypes: ['oauth:google-calendar' as const, 'personal_token:gitlab' as const], + }, + }, + { workspaceId: 'all-workspace', access: { mode: 'all' as const } }, + ] + const policy = buildOrganizationAccountAccessPolicy('group-1', grants) + + it('evaluates type and workspace together across OAuth, MCP, and personal tokens', () => { + expect(organizationAccountPolicyAllowsWorkspace(policy, 'mail-workspace', 'oauth:gmail')).toBe( + true + ) + expect( + organizationAccountPolicyAllowsWorkspace(policy, 'mail-workspace', 'oauth:google-calendar') + ).toBe(false) + expect( + organizationAccountPolicyAllowsWorkspace(policy, 'mail-workspace', 'mcp:fireflies') + ).toBe(true) + expect(organizationAccountPolicyAllowsWorkspace(policy, 'mail-workspace', 'mcp:granola')).toBe( + false + ) + expect( + organizationAccountPolicyAllowsWorkspace( + policy, + 'calendar-workspace', + 'personal_token:gitlab' + ) + ).toBe(true) + expect( + organizationAccountPolicyAllowsWorkspace(policy, 'mail-workspace', 'personal_token:gitlab') + ).toBe(false) + expect( + organizationAccountPolicyAllowsWorkspace(policy, 'unknown-workspace', 'oauth:gmail') + ).toBe(false) + }) + + it('keeps all-integration grants unconditional and round-trips selected grants', () => { + expect(organizationAccountPolicyAllowsWorkspace(policy, 'all-workspace', 'oauth:zoom')).toBe( + true + ) + expect( + policy.statements.find((statement) => statement.sid === 'WorkspaceCredentialAccess') + ).not.toHaveProperty('condition') + const restored = listOrganizationAccountWorkspaceGrants(policy) + for (const grant of grants) { + const match = restored.find((value) => value.workspaceId === grant.workspaceId) + expect(match?.access.mode).toBe(grant.access.mode) + if (grant.access.mode === 'selected' && match?.access.mode === 'selected') { + expect(new Set(match.access.credentialTypes)).toEqual(new Set(grant.access.credentialTypes)) + } + } + expect(organizationAccountPolicyAllowsWorkspace(policy, 'mail-workspace')).toBe(true) + }) + + it('fails closed for unknown types, duplicate types, and empty selections', () => { + for (const types of [[], ['oauth:unknown'], ['oauth:gmail', 'oauth:gmail']]) { + expect( + organizationAccountWorkspaceGrantsSchema.safeParse([ + { workspaceId: 'mail-workspace', access: { mode: 'selected', credentialTypes: types } }, + ]).success + ).toBe(false) + expect(() => + organizationAccountAccessPolicyCodec.parse( + { + ...policy, + statements: [ + { + ...policy.statements.find((statement) => statement.condition), + condition: { StringEquals: { 'credential_group:CredentialType': types } }, + }, + ], + }, + { type: 'credential_group', id: 'group-1' } + ) + ).toThrow() + } + }) + + it('rejects ambiguous overlapping or duplicate statements', () => { + const selected = policy.statements.find((statement) => statement.condition)! + const unrestricted = policy.statements.find((statement) => !statement.condition)! + for (const statements of [ + [selected, selected], + [selected, { ...unrestricted, principals: selected.principals }], + [{ ...selected, sid: 'WorkspaceCredentialAccess:oauth:unknown' }], + ]) { + expect(() => + organizationAccountAccessPolicyCodec.parse( + { ...policy, statements }, + { type: 'credential_group', id: 'group-1' } + ) + ).toThrow() + } }) }) diff --git a/apps/sim/lib/credential-groups/application/workspace-access-policy.ts b/apps/sim/lib/credential-groups/application/workspace-access-policy.ts index dd9af53ba90..bc037f9277c 100644 --- a/apps/sim/lib/credential-groups/application/workspace-access-policy.ts +++ b/apps/sim/lib/credential-groups/application/workspace-access-policy.ts @@ -1,34 +1,59 @@ import { z } from 'zod' +import { + ORGANIZATION_CREDENTIAL_TYPES, + type OrganizationCredentialType, +} from '@/lib/credential-groups/credential-types' import { ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT } from '@/lib/credential-groups/limits' +import { + type OrganizationAccountWorkspaceGrant, + organizationAccountWorkspaceGrantsSchema, + organizationCredentialTypeSchema, +} from '@/lib/credential-groups/workspace-grants' +import { CREDENTIAL_TYPE_CONDITION_KEY } from '@/lib/resource-policies/conditions/credential-type' import { evaluateResourcePolicy } from '@/lib/resource-policies/evaluator' import { workspaceResourcePolicyPrincipalSchema } from '@/lib/resource-policies/principals/workspace' import { CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION } from '@/lib/resource-policies/registry' import type { ResourcePolicyCodec } from '@/lib/resource-policies/types' -export const organizationAccountWorkspaceIdsSchema = z - .array(workspaceResourcePolicyPrincipalSchema.shape.workspaceId) - .max(ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT) - .refine((ids) => new Set(ids).size === ids.length, 'Workspace IDs must be unique') - const workspaceAccessStatementSchema = z .object({ - sid: z.literal('WorkspaceCredentialAccess'), + sid: z.string().min(1).max(256), effect: z.literal('allow'), actions: z.tuple([z.literal(CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION)]), principals: z .array(workspaceResourcePolicyPrincipalSchema) .min(1) .max(ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT), + condition: z + .object({ + StringEquals: z + .object({ [CREDENTIAL_TYPE_CONDITION_KEY]: organizationCredentialTypeSchema }) + .strict(), + }) + .strict() + .optional(), }) .strict() - .refine( - ({ principals }) => - principals.every( + .superRefine((statement, context) => { + const type = statement.condition?.StringEquals[CREDENTIAL_TYPE_CONDITION_KEY] + const expectedSid = type ? `WorkspaceCredentialAccess:${type}` : 'WorkspaceCredentialAccess' + if (statement.sid !== expectedSid) + context.addIssue({ + code: 'custom', + message: 'Workspace statement ID must match its credential type', + }) + if ( + !statement.principals.every( (principal, index) => - index === 0 || principals[index - 1].workspaceId < principal.workspaceId - ), - 'Workspace principals must be sorted and unique' - ) + index === 0 || statement.principals[index - 1].workspaceId < principal.workspaceId + ) + ) { + context.addIssue({ + code: 'custom', + message: 'Workspace principals must be sorted and unique', + }) + } + }) export const organizationAccountAccessPolicySchema = z .object({ @@ -36,9 +61,34 @@ export const organizationAccountAccessPolicySchema = z resource: z .object({ type: z.literal('credential_group'), id: z.string().min(1).max(128) }) .strict(), - statements: z.array(workspaceAccessStatementSchema).max(1), + statements: z + .array(workspaceAccessStatementSchema) + .max(ORGANIZATION_CREDENTIAL_TYPES.length + 1), }) .strict() + .superRefine(({ statements }, context) => { + const statementIds = new Set(statements.map((statement) => statement.sid)) + if (statementIds.size !== statements.length) + context.addIssue({ code: 'custom', message: 'Workspace statements must be unique' }) + const unrestricted = new Set( + statements + .filter((statement) => !statement.condition) + .flatMap((statement) => statement.principals.map((principal) => principal.workspaceId)) + ) + const workspaceIds = new Set() + for (const statement of statements) { + for (const principal of statement.principals) { + workspaceIds.add(principal.workspaceId) + if (statement.condition && unrestricted.has(principal.workspaceId)) + context.addIssue({ + code: 'custom', + message: 'A workspace cannot have both all and selected credential access', + }) + } + } + if (workspaceIds.size > ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT) + context.addIssue({ code: 'custom', message: 'Too many workspace grants' }) + }) export type OrganizationAccountAccessPolicy = z.output @@ -49,51 +99,93 @@ export const organizationAccountAccessPolicyCodec: ResourcePolicyCodec< resourceType: 'credential_group', parse(value, expected) { const document = organizationAccountAccessPolicySchema.parse(value) - if (document.resource.type !== expected.type || document.resource.id !== expected.id) { - throw new Error('Connected accounts policy does not match its canonical group') - } + if (document.resource.type !== expected.type || document.resource.id !== expected.id) + throw new Error('Credential Groups policy does not match its canonical group') return document }, } export function buildOrganizationAccountAccessPolicy( credentialGroupId: string, - workspaceIds: string[] + grants: OrganizationAccountWorkspaceGrant[] ): OrganizationAccountAccessPolicy { - const ids = organizationAccountWorkspaceIdsSchema.parse(workspaceIds).sort() + const parsed = organizationAccountWorkspaceGrantsSchema.parse(grants) + const byType = new Map() + for (const { workspaceId, access } of parsed) { + const types = access.mode === 'all' ? ['all' as const] : access.credentialTypes + for (const type of types) { + const workspaces = byType.get(type) ?? [] + workspaces.push(workspaceId) + byType.set(type, workspaces) + } + } return organizationAccountAccessPolicySchema.parse({ version: 2, resource: { type: 'credential_group', id: credentialGroupId }, - statements: ids.length - ? [ - { - sid: 'WorkspaceCredentialAccess', - effect: 'allow', - actions: [CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION], - principals: ids.map((workspaceId) => ({ type: 'workspace', workspaceId })), - }, - ] - : [], + statements: [...byType] + .sort(([left], [right]) => left.localeCompare(right)) + .map(([type, workspaceIds]) => ({ + sid: type === 'all' ? 'WorkspaceCredentialAccess' : `WorkspaceCredentialAccess:${type}`, + effect: 'allow', + actions: [CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION], + principals: workspaceIds.sort().map((workspaceId) => ({ type: 'workspace', workspaceId })), + ...(type === 'all' + ? {} + : { condition: { StringEquals: { [CREDENTIAL_TYPE_CONDITION_KEY]: type } } }), + })), }) } +export function listOrganizationAccountWorkspaceGrants( + document: OrganizationAccountAccessPolicy +): OrganizationAccountWorkspaceGrant[] { + const grants = new Map() + for (const statement of document.statements) { + const type = statement.condition?.StringEquals[CREDENTIAL_TYPE_CONDITION_KEY] + for (const { workspaceId } of statement.principals) { + if (!type) { + grants.set(workspaceId, { workspaceId, access: { mode: 'all' } }) + } else { + const existing = grants.get(workspaceId) + if (existing?.access.mode === 'all') throw new Error('Overlapping workspace grants') + if (existing) existing.access.credentialTypes.push(type) + else + grants.set(workspaceId, { + workspaceId, + access: { mode: 'selected', credentialTypes: [type] }, + }) + } + } + } + return [...grants.values()].sort((left, right) => + left.workspaceId.localeCompare(right.workspaceId) + ) +} + export function listOrganizationAccountWorkspaceIds( document: OrganizationAccountAccessPolicy ): string[] { - return document.statements.flatMap((statement) => - statement.principals.map((principal) => principal.workspaceId) - ) + return [ + ...new Set( + document.statements.flatMap((statement) => + statement.principals.map((principal) => principal.workspaceId) + ) + ), + ].sort() } +/** Tests the resource policy with the canonical integration, or any registered integration for a catalog entry point. */ export function organizationAccountPolicyAllowsWorkspace( document: OrganizationAccountAccessPolicy, - workspaceId: string + workspaceId: string, + credentialType?: OrganizationCredentialType ): boolean { - return ( - evaluateResourcePolicy({ - document, - action: CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION, - facts: { currentWorkspaceId: workspaceId }, - }).decision === 'allow' + return (credentialType ? [credentialType] : ORGANIZATION_CREDENTIAL_TYPES).some( + (type) => + evaluateResourcePolicy({ + document, + action: CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION, + facts: { currentWorkspaceId: workspaceId, credentialType: type }, + }).decision === 'allow' ) } diff --git a/apps/sim/lib/credential-groups/application/workspace-organization-accounts.test.ts b/apps/sim/lib/credential-groups/application/workspace-organization-accounts.test.ts new file mode 100644 index 00000000000..dbb592c9a22 --- /dev/null +++ b/apps/sim/lib/credential-groups/application/workspace-organization-accounts.test.ts @@ -0,0 +1,87 @@ +/** @vitest-environment node */ +import { queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const mocks = vi.hoisted(() => ({ group: vi.fn(), policy: vi.fn() })) +vi.mock('@/lib/credential-groups/application/context', () => ({ + resolveCredentialGroupWorkspaceContext: async () => ({ + workspaceId: 'workspace-1', + workspaceOrganizationId: 'org-1', + allowPersonalApiKeys: true, + }), +})) +vi.mock('@sim/platform-authz/workspace', () => ({ + resolveEffectiveWorkspacePermission: vi.fn().mockResolvedValue('read'), + permissionSatisfies: (permission: string, required: string) => permission === required, +})) +vi.mock('@/lib/credential-groups/credentials', () => ({ + loadScopedAccountsCredentialListContext: mocks.group, +})) +vi.mock('@/lib/credential-groups/scoped-availability', () => ({ + isScopedCredentialGroupsAvailable: vi.fn().mockResolvedValue(true), +})) +vi.mock('@/lib/resource-policies/repository', () => ({ requireResourcePolicy: mocks.policy })) + +import { buildOrganizationAccountAccessPolicy } from '@/lib/credential-groups/application/workspace-access-policy' +import { getWorkspaceOrganizationAccounts } from '@/lib/credential-groups/application/workspace-organization-accounts' + +function read() { + return getWorkspaceOrganizationAccounts.execute({ + principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' }, + input: { workspaceId: 'workspace-1' }, + }) +} + +describe('workspace organization provider projection', () => { + beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + queueTableRows(schemaMock.organization, [{ name: 'Organization' }]) + queueTableRows(schemaMock.member, [{ role: 'member' }]) + queueTableRows(schemaMock.mcpServers, [{ connectorId: 'fireflies' }]) + mocks.group.mockResolvedValue({ + credentialGroupId: 'group-1', + status: 'active', + options: [ + { provider: 'gmail', status: 'active' }, + { provider: 'google-calendar', status: 'active' }, + { provider: 'retired-provider', status: 'disabled' }, + ], + }) + mocks.policy.mockResolvedValue({ + document: buildOrganizationAccountAccessPolicy('group-1', [ + { workspaceId: 'workspace-1', access: { mode: 'all' } }, + ]), + }) + }) + + it('ignores disabled legacy options before validating active providers', async () => { + const result = await read() + expect(result.providers.map(({ id }) => id)).toEqual(['google-email', 'google-calendar']) + expect(result.mcpProviders.map(({ id }) => id)).toEqual(['fireflies']) + }) + + it('projects only credential types allowed for the current workspace', async () => { + mocks.policy.mockResolvedValue({ + document: buildOrganizationAccountAccessPolicy('group-1', [ + { + workspaceId: 'workspace-1', + access: { mode: 'selected', credentialTypes: ['oauth:gmail'] }, + }, + ]), + }) + const result = await read() + expect(result.allowed).toBe(true) + expect(result.providers.map(({ id }) => id)).toEqual(['google-email']) + expect(result.mcpProviders).toEqual([]) + }) + + it('fails fast for an unregistered active provider', async () => { + mocks.group.mockResolvedValue({ + credentialGroupId: 'group-1', + status: 'active', + options: [{ provider: 'unknown', status: 'active' }], + }) + await expect(read()).rejects.toThrow('Unsupported organization provider: unknown') + }) +}) diff --git a/apps/sim/lib/credential-groups/application/workspace-organization-accounts.ts b/apps/sim/lib/credential-groups/application/workspace-organization-accounts.ts index c33cf518f70..e09baa58387 100644 --- a/apps/sim/lib/credential-groups/application/workspace-organization-accounts.ts +++ b/apps/sim/lib/credential-groups/application/workspace-organization-accounts.ts @@ -77,7 +77,16 @@ export const getWorkspaceOrganizationAccounts = defineAuthorizedWorkspaceUseCase result.allowed = organizationAccountPolicyAllowsWorkspace(policy.document, context.workspaceId) if (!result.allowed) return result result.providers = group.options - .filter((option) => option.status === 'active') + .filter((option) => { + if (option.status !== 'active') return false + if (!isCredentialGroupProvider(option.provider)) + throw new Error(`Unsupported organization provider: ${option.provider}`) + return organizationAccountPolicyAllowsWorkspace( + policy.document, + context.workspaceId, + `oauth:${option.provider}` + ) + }) .map((option) => { if (!isCredentialGroupProvider(option.provider)) throw new Error(`Unsupported organization provider: ${option.provider}`) @@ -95,11 +104,17 @@ export const getWorkspaceOrganizationAccounts = defineAuthorizedWorkspaceUseCase isNull(mcpServers.deletedAt) ) ) - result.mcpProviders = servers.map((server) => { + result.mcpProviders = servers.flatMap((server) => { if (!server.connectorId) throw new Error('Organization MCP provider is missing its connector ID') const connector = getManagedMcpConnector(server.connectorId) - return { id: connector.id, label: connector.name } + return organizationAccountPolicyAllowsWorkspace( + policy.document, + context.workspaceId, + `mcp:${connector.id}` + ) + ? [{ id: connector.id, label: connector.name }] + : [] }) return result }, diff --git a/apps/sim/lib/credential-groups/credential-types.ts b/apps/sim/lib/credential-groups/credential-types.ts new file mode 100644 index 00000000000..2cb90078aed --- /dev/null +++ b/apps/sim/lib/credential-groups/credential-types.ts @@ -0,0 +1,42 @@ +import { + MANAGED_MCP_CONNECTOR_IDS, + MANAGED_MCP_CONNECTORS, +} from '@/lib/credential-groups/managed-mcp-connectors' +import { + CREDENTIAL_GROUP_PROVIDER_IDS, + getCredentialGroupProviderFromProviderId, + getCredentialGroupProviderService, +} from '@/lib/credential-groups/providers' + +export type OrganizationCredentialType = + | `oauth:${(typeof CREDENTIAL_GROUP_PROVIDER_IDS)[number]}` + | `mcp:${(typeof MANAGED_MCP_CONNECTOR_IDS)[number]}` + | 'personal_token:gitlab' + +export const ORGANIZATION_CREDENTIAL_TYPES: readonly OrganizationCredentialType[] = [ + ...CREDENTIAL_GROUP_PROVIDER_IDS.map((provider) => `oauth:${provider}` as const), + ...MANAGED_MCP_CONNECTOR_IDS.map((provider) => `mcp:${provider}` as const), + 'personal_token:gitlab', +] + +export function isOrganizationCredentialType(value: string): value is OrganizationCredentialType { + return ORGANIZATION_CREDENTIAL_TYPES.some((type) => type === value) +} + +export function organizationOAuthCredentialType(providerId: string): OrganizationCredentialType { + return `oauth:${getCredentialGroupProviderFromProviderId(providerId)}` +} + +export function getOrganizationCredentialTypeCatalog() { + return [ + ...CREDENTIAL_GROUP_PROVIDER_IDS.map((provider) => ({ + id: `oauth:${provider}` as const, + label: getCredentialGroupProviderService(provider).name, + })), + ...MANAGED_MCP_CONNECTOR_IDS.map((provider) => ({ + id: `mcp:${provider}` as const, + label: MANAGED_MCP_CONNECTORS[provider].name, + })), + { id: 'personal_token:gitlab' as const, label: 'GitLab' }, + ].sort((left, right) => left.label.localeCompare(right.label)) +} diff --git a/apps/sim/lib/credential-groups/mcp-connections.ts b/apps/sim/lib/credential-groups/mcp-connections.ts index 12a891a9427..7e65efa40bc 100644 --- a/apps/sim/lib/credential-groups/mcp-connections.ts +++ b/apps/sim/lib/credential-groups/mcp-connections.ts @@ -32,6 +32,7 @@ interface ListCredentialGroupMcpConnectionReferencesInput { email?: string mcpServerId?: string connectorId?: string + allowedConnectorIds?: readonly string[] } function decodeToolNames(value: unknown): string[] { @@ -52,15 +53,23 @@ export async function listCredentialGroupMcpConnectionReferences({ email, mcpServerId, connectorId, + allowedConnectorIds, }: ListCredentialGroupMcpConnectionReferencesInput): Promise<{ mcpConnections: CredentialGroupMcpConnectionReference[] nextCursor: string | null }> { + if (allowedConnectorIds?.length === 0) { + if (cursor) throw new CredentialGroupMcpConnectionCursorNotFoundError() + return { mcpConnections: [], nextCursor: null } + } const ownerScope = resourceScopeFromOwner({ workspaceId, organizationId }) const scope = () => and( resourceScopeCondition(credential, ownerScope), eq(credential.type, 'managed_mcp'), + allowedConnectorIds + ? inArray(mcpServers.managedConnectorId, [...allowedConnectorIds]) + : undefined, eq(credential.managedOauthStatus, 'active'), eq(credential.mcpOauthConfigVersion, mcpServers.oauthConfigVersion), eq(credentialGroup.id, credentialGroupId), diff --git a/apps/sim/lib/credential-groups/trigger.test.ts b/apps/sim/lib/credential-groups/trigger.test.ts index 1f8d1ac446d..62c6cf2a663 100644 --- a/apps/sim/lib/credential-groups/trigger.test.ts +++ b/apps/sim/lib/credential-groups/trigger.test.ts @@ -73,7 +73,13 @@ describe('Credential Group trigger delivery', () => { beforeEach(() => { vi.clearAllMocks() mocks.requirePolicy.mockResolvedValue({ - document: buildOrganizationAccountAccessPolicy('group-1', ['workspace-1', 'workspace-2']), + document: buildOrganizationAccountAccessPolicy( + 'group-1', + ['workspace-1', 'workspace-2'].map((workspaceId) => ({ + workspaceId, + access: { mode: 'all' as const }, + })) + ), }) mocks.resolveWorkspace.mockImplementation(async (workspaceId: string) => ({ workspaceId, @@ -108,6 +114,29 @@ describe('Credential Group trigger delivery', () => { ) }) + it('discovers subscribers only for the event integration and rechecks that type before delivery', async () => { + mocks.requirePolicy.mockResolvedValue({ + document: buildOrganizationAccountAccessPolicy('group-1', [ + { + workspaceId: 'workspace-1', + access: { mode: 'selected', credentialTypes: ['oauth:gmail'] }, + }, + { + workspaceId: 'workspace-2', + access: { mode: 'selected', credentialTypes: ['oauth:google-calendar'] }, + }, + ]), + }) + mocks.fetchSubscriptions.mockResolvedValue([subscription({ workflowId: 'allowed' })]) + await fireCredentialGroupTrigger(EVENT) + expect(mocks.fetchSubscriptions).toHaveBeenCalledWith('org-1', ['workspace-1']) + expect(mocks.requireAccess).toHaveBeenCalledWith( + expect.objectContaining({ workspaceId: 'workspace-1' }), + 'oauth:gmail' + ) + expect(mocks.processEvent).toHaveBeenCalledOnce() + }) + it('does not scan subscriptions when no workspace has access', async () => { mocks.requirePolicy.mockResolvedValue({ document: buildOrganizationAccountAccessPolicy('group-1', []), diff --git a/apps/sim/lib/credential-groups/trigger.ts b/apps/sim/lib/credential-groups/trigger.ts index c9fa7e964a6..19d5f896312 100644 --- a/apps/sim/lib/credential-groups/trigger.ts +++ b/apps/sim/lib/credential-groups/trigger.ts @@ -8,8 +8,14 @@ import { import { listOrganizationAccountWorkspaceIds, organizationAccountAccessPolicyCodec, + organizationAccountPolicyAllowsWorkspace, } from '@/lib/credential-groups/application/workspace-access-policy' +import { + type OrganizationCredentialType, + organizationOAuthCredentialType, +} from '@/lib/credential-groups/credential-types' import type { ManagedMcpConnectorId } from '@/lib/credential-groups/managed-mcp-connectors' +import { getManagedMcpConnector } from '@/lib/credential-groups/managed-mcp-connectors' import type { CredentialGroupProvider } from '@/lib/credential-groups/providers' import { CREDENTIAL_GROUP_EVENT_TRIGGER_ID, @@ -121,7 +127,15 @@ export async function fireCredentialGroupTrigger( resourceId: event.credentialGroupId, codec: organizationAccountAccessPolicyCodec, }) - const allowedWorkspaceIds = listOrganizationAccountWorkspaceIds(policy.document) + const credentialType: OrganizationCredentialType | undefined = + event.event === 'form_submitted' + ? undefined + : event.credential.mcpServerId + ? `mcp:${getManagedMcpConnector(event.credential.provider).id}` + : organizationOAuthCredentialType(event.credential.providerId) + const allowedWorkspaceIds = listOrganizationAccountWorkspaceIds(policy.document).filter((id) => + organizationAccountPolicyAllowsWorkspace(policy.document, id, credentialType) + ) if (allowedWorkspaceIds.length === 0) return const subscriptions = await fetchCredentialGroupTriggerSubscriptions( event.organizationId, @@ -141,7 +155,7 @@ export async function fireCredentialGroupTrigger( const context = await resolveOrganizationAccountsWorkspaceContext(workflow.workspaceId) if (context.credentialGroupId !== event.credentialGroupId || context.status !== 'active') continue - await requireOrganizationAccountsWorkspaceAccess(context) + await requireOrganizationAccountsWorkspaceAccess(context, credentialType) } catch (error) { /** Revocations and workspace moves remove subscribers between discovery and delivery. */ if ( diff --git a/apps/sim/lib/credential-groups/workspace-grants.ts b/apps/sim/lib/credential-groups/workspace-grants.ts new file mode 100644 index 00000000000..b982dd53a3e --- /dev/null +++ b/apps/sim/lib/credential-groups/workspace-grants.ts @@ -0,0 +1,42 @@ +import { z } from 'zod' +import { ORGANIZATION_CREDENTIAL_TYPES } from '@/lib/credential-groups/credential-types' +import { ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT } from '@/lib/credential-groups/limits' +import { workspaceResourcePolicyPrincipalSchema } from '@/lib/resource-policies/principals/workspace' + +export const organizationCredentialTypeSchema = z.enum(ORGANIZATION_CREDENTIAL_TYPES, { + error: 'Unknown credential type', +}) + +export const organizationAccountWorkspaceGrantSchema = z + .object({ + workspaceId: workspaceResourcePolicyPrincipalSchema.shape.workspaceId, + access: z.discriminatedUnion('mode', [ + z.object({ mode: z.literal('all') }).strict(), + z + .object({ + mode: z.literal('selected'), + credentialTypes: z + .array(organizationCredentialTypeSchema) + .min(1, 'Select at least one credential type') + .max(ORGANIZATION_CREDENTIAL_TYPES.length) + .refine( + (types) => new Set(types).size === types.length, + 'Credential types must be unique' + ), + }) + .strict(), + ]), + }) + .strict() + +export const organizationAccountWorkspaceGrantsSchema = z + .array(organizationAccountWorkspaceGrantSchema) + .max(ORGANIZATION_ACCOUNT_WORKSPACE_LIMIT) + .refine( + (grants) => new Set(grants.map((grant) => grant.workspaceId)).size === grants.length, + 'Workspace grants must be unique' + ) + +export type OrganizationAccountWorkspaceGrant = z.output< + typeof organizationAccountWorkspaceGrantSchema +> diff --git a/apps/sim/lib/credentials/application/personal-connection.test.ts b/apps/sim/lib/credentials/application/personal-connection.test.ts index c1b3190ffb6..c2544fc67c5 100644 --- a/apps/sim/lib/credentials/application/personal-connection.test.ts +++ b/apps/sim/lib/credentials/application/personal-connection.test.ts @@ -82,7 +82,10 @@ describe('personal connection launch', () => { mocks.organizationMembership.mockResolvedValue({ userId: 'viewer', role: 'member' }) mocks.available.mockResolvedValue(true) mocks.policy.mockResolvedValue({ - document: buildOrganizationAccountAccessPolicy('canonical-group', ['workspace']), + document: buildOrganizationAccountAccessPolicy( + 'canonical-group', + ['workspace'].map((workspaceId) => ({ workspaceId, access: { mode: 'all' as const } })) + ), }) mocks.catalog.mockResolvedValue([ { diff --git a/apps/sim/lib/credentials/application/personal-credentials.test.ts b/apps/sim/lib/credentials/application/personal-credentials.test.ts index 295f4e04fc6..ccad2c1d180 100644 --- a/apps/sim/lib/credentials/application/personal-credentials.test.ts +++ b/apps/sim/lib/credentials/application/personal-credentials.test.ts @@ -2,6 +2,7 @@ * @vitest-environment node */ import type { DelegatedPrincipal, Principal } from '@sim/auth/principal' +import { queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ @@ -70,6 +71,7 @@ function delegatedPrincipal(overrides: Partial = {}): Delega describe('personal credential application access', () => { beforeEach(() => { vi.clearAllMocks() + resetDbChainMock() mocks.loadWorkspace.mockResolvedValue(workspaceContext) mocks.resolvePermission.mockResolvedValue('read') mocks.listPersonal.mockResolvedValue([personalCredential]) @@ -114,6 +116,16 @@ describe('personal credential application access', () => { instanceUrl: 'https://gitlab.example.com', } mocks.listTokens.mockResolvedValue([token]) + queueTableRows(schemaMock.credential, [ + { + ...token, + organizationId: null, + workspaceId: 'workspace-1', + groupId: 'legacy-group', + groupOrganizationId: null, + groupWorkspaceId: 'workspace-1', + }, + ]) const result = await listPersonalCredentials.execute({ principal, input: { workspaceId: 'workspace-1' }, @@ -136,6 +148,16 @@ describe('personal credential application access', () => { it('authorizes a managed account returned by the same personal policy', async () => { const managed = { ...personalCredential, providerId: 'slack', type: 'managed_oauth' as const } mocks.listPersonal.mockResolvedValue([managed]) + queueTableRows(schemaMock.credential, [ + { + ...managed, + organizationId: null, + workspaceId: 'workspace-1', + groupId: 'legacy-group', + groupOrganizationId: null, + groupWorkspaceId: 'workspace-1', + }, + ]) const result = await authorizePersonalCredential.execute({ principal, diff --git a/apps/sim/lib/credentials/application/personal-credentials.ts b/apps/sim/lib/credentials/application/personal-credentials.ts index 725ee40fc98..9271ff4c449 100644 --- a/apps/sim/lib/credentials/application/personal-credentials.ts +++ b/apps/sim/lib/credentials/application/personal-credentials.ts @@ -3,6 +3,7 @@ import { defineAuthorizedWorkspaceUseCase } from '@/lib/core/application' import { OrchestrationError } from '@/lib/core/orchestration/types' import { credentialDelegationPolicy } from '@/lib/credentials/application/authorization' import { credentialOperations } from '@/lib/credentials/application/operations' +import { filterWorkspaceAccountCredentials } from '@/lib/credentials/application/workspace-account-visibility' import { getPersonalOAuthCredentials, type PersonalOAuthCredential, @@ -33,7 +34,10 @@ export const listPersonalCredentials = defineAuthorizedWorkspaceUseCase({ getPersonalTokenCredentials(context.workspaceId, userId), ]) return { - credentials: [...oauthCredentials, ...tokenCredentials], + credentials: await filterWorkspaceAccountCredentials(context, [ + ...oauthCredentials, + ...tokenCredentials, + ]), } }, }) @@ -57,7 +61,8 @@ export const authorizePersonalCredential = defineAuthorizedWorkspaceUseCase({ input.credentialId ) const providerIds = providerIdsForService(input.expectedProviderId) - const credential = credentials.find( + const visible = await filterWorkspaceAccountCredentials(context, credentials) + const credential = visible.find( (entry) => entry.id === input.credentialId && providerIds.includes(entry.providerId) ) if (!credential) { diff --git a/apps/sim/lib/credentials/application/resolve-personal-token.test.ts b/apps/sim/lib/credentials/application/resolve-personal-token.test.ts index 92b3969f17f..5bd8e7eb18f 100644 --- a/apps/sim/lib/credentials/application/resolve-personal-token.test.ts +++ b/apps/sim/lib/credentials/application/resolve-personal-token.test.ts @@ -8,6 +8,8 @@ const mocks = vi.hoisted(() => ({ decrypt: vi.fn(), audit: vi.fn(), enrollment: vi.fn(), + policy: vi.fn(), + available: vi.fn(), })) vi.mock('@/lib/credentials/application/credential-context', () => ({ resolveCredentialApplicationContext: mocks.context, @@ -27,6 +29,12 @@ vi.mock('@sim/audit', () => ({ recordAudit: mocks.audit, })) +vi.mock('@/lib/resource-policies/repository', () => ({ requireResourcePolicy: mocks.policy })) +vi.mock('@/lib/credential-groups/scoped-availability', () => ({ + isScopedCredentialGroupsAvailable: mocks.available, +})) + +import { buildOrganizationAccountAccessPolicy } from '@/lib/credential-groups/application/workspace-access-policy' import { resolvePersonalToken } from '@/lib/credentials/application/resolve-personal-token' const principal = { kind: 'session', userId: 'owner', sessionId: 'session' } as const @@ -116,6 +124,45 @@ describe('authorized personal token resolution', () => { expect(mocks.decrypt).not.toHaveBeenCalled() expect(mocks.audit).not.toHaveBeenCalled() }) + it('authorizes organization token type before decrypting and rechecks revocation', async () => { + const organizationToken = { ...current, workspaceId: null, organizationId: 'org' } + mocks.context.mockResolvedValue({ + ...context, + workspaceOrganizationId: 'org', + credential: organizationToken, + }) + mocks.access.mockResolvedValue({ + credential: organizationToken, + member: null, + hasWorkspaceAccess: true, + canWriteWorkspace: false, + isAdmin: true, + }) + mocks.enrollment.mockResolvedValue({ credentialGroupId: 'group' }) + mocks.available.mockResolvedValue(true) + mocks.policy.mockResolvedValue({ + document: buildOrganizationAccountAccessPolicy('group', [ + { + workspaceId: 'ws', + access: { mode: 'selected', credentialTypes: ['personal_token:gitlab'] }, + }, + ]), + }) + await expect(resolvePersonalToken.execute({ principal, input })).resolves.toMatchObject({ + accessToken: 'secret', + }) + mocks.decrypt.mockClear() + mocks.policy.mockResolvedValue({ + document: buildOrganizationAccountAccessPolicy('group', [ + { workspaceId: 'ws', access: { mode: 'selected', credentialTypes: ['oauth:gmail'] } }, + ]), + }) + await expect(resolvePersonalToken.execute({ principal, input })).rejects.toMatchObject({ + code: 'forbidden', + }) + expect(mocks.decrypt).not.toHaveBeenCalled() + }) + it('refuses revoked workspace access before secret resolution', async () => { mocks.permission.mockResolvedValue(null) await expect(resolvePersonalToken.execute({ principal, input })).rejects.toThrow( diff --git a/apps/sim/lib/credentials/application/resolve-personal-token.ts b/apps/sim/lib/credentials/application/resolve-personal-token.ts index 1901ee3aa19..3b0613df538 100644 --- a/apps/sim/lib/credentials/application/resolve-personal-token.ts +++ b/apps/sim/lib/credentials/application/resolve-personal-token.ts @@ -2,6 +2,7 @@ import { AuditAction, AuditResourceType } from '@sim/audit' import { requirePrincipalSubjectUserId } from '@sim/auth/principal' import { OrchestrationError } from '@/lib/core/orchestration/types' import { resourceScopeFields, resourceScopeFromOwner } from '@/lib/core/resource-scope' +import { requireOrganizationAccountsWorkspaceAccess } from '@/lib/credential-groups/application/organization-workspace-access' import { defineAuthorizedCredentialUseCase } from '@/lib/credentials/application/authorized-credential-use-case' import { resolveCredentialApplicationContext } from '@/lib/credentials/application/credential-context' import { credentialOperations } from '@/lib/credentials/application/operations' @@ -41,11 +42,21 @@ export const resolvePersonalToken = defineAuthorizedCredentialUseCase({ 'Connect your own active personal token for this integration' ) } - await requirePersonalTokenEnrollment({ + const enrollment = await requirePersonalTokenEnrollment({ ...resourceScopeFields(resourceScopeFromOwner(current)), userId, enrollmentId: current.credentialGroupEnrollmentId, }) + if (current.organizationId) { + await requireOrganizationAccountsWorkspaceAccess( + { + ...context, + organizationId: current.organizationId, + credentialGroupId: enrollment.credentialGroupId, + }, + 'personal_token:gitlab' + ) + } const accessToken = await decryptPersonalToken(current.encryptedPersonalToken, { providerId: 'gitlab', ownerUserId: userId, diff --git a/apps/sim/lib/credentials/application/workspace-account-visibility.test.ts b/apps/sim/lib/credentials/application/workspace-account-visibility.test.ts new file mode 100644 index 00000000000..69d94bd8b47 --- /dev/null +++ b/apps/sim/lib/credentials/application/workspace-account-visibility.test.ts @@ -0,0 +1,154 @@ +/** @vitest-environment node */ +import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const mocks = vi.hoisted(() => ({ policy: vi.fn(), available: vi.fn() })) +vi.mock('@/lib/resource-policies/repository', () => ({ requireResourcePolicy: mocks.policy })) +vi.mock('@/lib/credential-groups/scoped-availability', () => ({ + isScopedCredentialGroupsAvailable: mocks.available, +})) + +import { buildOrganizationAccountAccessPolicy } from '@/lib/credential-groups/application/workspace-access-policy' +import { filterWorkspaceAccountCredentials } from '@/lib/credentials/application/workspace-account-visibility' + +const context = { workspaceId: 'ws', workspaceOrganizationId: 'org', allowPersonalApiKeys: true } +const entries = [ + { id: 'ordinary', type: 'oauth', providerId: 'google-email' }, + { id: 'mail', type: 'managed_oauth', providerId: 'google-email' }, + { id: 'calendar', type: 'managed_oauth', providerId: 'google-calendar' }, + { id: 'token', type: 'personal_token', providerId: 'gitlab' }, +] +const bindings = entries.slice(1).map((entry) => ({ + ...entry, + organizationId: 'org', + workspaceId: null, + groupId: 'group', + groupOrganizationId: 'org', + groupWorkspaceId: null, +})) + +beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + mocks.available.mockResolvedValue(true) + mocks.policy.mockResolvedValue({ + document: buildOrganizationAccountAccessPolicy('group', [ + { + workspaceId: 'ws', + access: { mode: 'selected', credentialTypes: ['oauth:gmail', 'personal_token:gitlab'] }, + }, + ]), + }) +}) + +describe('workspace organization credential visibility', () => { + it('filters canonical OAuth and token types with a single policy read while preserving ordinary accounts', async () => { + queueTableRows(schemaMock.credential, bindings) + expect(await filterWorkspaceAccountCredentials(context, entries)).toEqual([ + entries[0], + entries[1], + entries[3], + ]) + expect(mocks.policy).toHaveBeenCalledExactlyOnceWith( + expect.objectContaining({ organizationId: 'org', resourceId: 'group' }) + ) + expect(dbChainMockFns.select).toHaveBeenCalledExactlyOnceWith({ + id: schemaMock.credential.id, + organizationId: schemaMock.credential.organizationId, + workspaceId: schemaMock.credential.workspaceId, + groupId: schemaMock.credentialGroup.id, + groupOrganizationId: schemaMock.credentialGroup.organizationId, + groupWorkspaceId: schemaMock.credentialGroup.workspaceId, + providerId: schemaMock.credential.providerId, + type: schemaMock.credential.type, + }) + }) + + it('rechecks revocation and does not reuse a previously allowed selection', async () => { + queueTableRows(schemaMock.credential, bindings) + await filterWorkspaceAccountCredentials(context, entries) + mocks.policy.mockResolvedValue({ document: buildOrganizationAccountAccessPolicy('group', []) }) + queueTableRows(schemaMock.credential, bindings) + expect(await filterWorkspaceAccountCredentials(context, entries)).toEqual([entries[0]]) + }) + + it.each([null, 'other-org'])( + 'hides organization accounts when the workspace belongs to %s', + async (workspaceOrganizationId) => { + queueTableRows(schemaMock.credential, bindings) + expect( + await filterWorkspaceAccountCredentials({ ...context, workspaceOrganizationId }, entries) + ).toEqual([entries[0]]) + expect(mocks.policy).not.toHaveBeenCalled() + } + ) + + it('fails closed for removed bindings and a disabled feature', async () => { + queueTableRows(schemaMock.credential, []) + expect(await filterWorkspaceAccountCredentials(context, entries)).toEqual([entries[0]]) + mocks.available.mockResolvedValue(false) + queueTableRows(schemaMock.credential, bindings) + expect(await filterWorkspaceAccountCredentials(context, entries)).toEqual([entries[0]]) + expect(mocks.policy).not.toHaveBeenCalled() + }) + + it('preserves independently managed workspace accounts', async () => { + queueTableRows( + schemaMock.credential, + bindings.map((binding) => ({ + ...binding, + organizationId: null, + workspaceId: 'ws', + groupOrganizationId: null, + groupWorkspaceId: 'ws', + })) + ) + expect(await filterWorkspaceAccountCredentials(context, entries)).toEqual(entries) + expect(mocks.available).not.toHaveBeenCalled() + expect(mocks.policy).not.toHaveBeenCalled() + }) + + it.each([ + { groupOrganizationId: 'other-org', groupWorkspaceId: null }, + { groupOrganizationId: null, groupWorkspaceId: 'ws' }, + ])('rejects mismatched group ownership before loading policy: %j', async (owner) => { + queueTableRows( + schemaMock.credential, + bindings.map((binding) => ({ ...binding, ...owner })) + ) + await expect(filterWorkspaceAccountCredentials(context, entries)).rejects.toThrow( + 'Credential and enrollment group owners do not match' + ) + expect(mocks.policy).not.toHaveBeenCalled() + }) + + it('hides independently managed credentials that moved to another workspace', async () => { + queueTableRows( + schemaMock.credential, + bindings.map((binding) => ({ + ...binding, + organizationId: null, + workspaceId: 'other-ws', + groupOrganizationId: null, + groupWorkspaceId: 'other-ws', + })) + ) + expect(await filterWorkspaceAccountCredentials(context, entries)).toEqual([entries[0]]) + expect(mocks.policy).not.toHaveBeenCalled() + }) + + it('throws for malformed policy or a changed canonical provider instead of granting access', async () => { + queueTableRows(schemaMock.credential, bindings) + mocks.policy.mockRejectedValueOnce(new Error('Malformed policy')) + await expect(filterWorkspaceAccountCredentials(context, entries)).rejects.toThrow( + 'Malformed policy' + ) + queueTableRows( + schemaMock.credential, + bindings.map((binding) => ({ ...binding, providerId: 'slack' })) + ) + await expect(filterWorkspaceAccountCredentials(context, entries)).rejects.toThrow( + 'binding changed' + ) + }) +}) diff --git a/apps/sim/lib/credentials/application/workspace-account-visibility.ts b/apps/sim/lib/credentials/application/workspace-account-visibility.ts new file mode 100644 index 00000000000..a472cd895f0 --- /dev/null +++ b/apps/sim/lib/credentials/application/workspace-account-visibility.ts @@ -0,0 +1,98 @@ +import { db } from '@sim/db' +import { credential, credentialGroup, credentialGroupEnrollment } from '@sim/db/schema' +import { eq, inArray } from 'drizzle-orm' +import type { WorkspaceAuthorizationContext } from '@/lib/core/application' +import { resourceScopeFromOwner, sameResourceScope } from '@/lib/core/resource-scope' +import { + type OrganizationAccountAccessPolicy, + organizationAccountAccessPolicyCodec, + organizationAccountPolicyAllowsWorkspace, +} from '@/lib/credential-groups/application/workspace-access-policy' +import { organizationOAuthCredentialType } from '@/lib/credential-groups/credential-types' +import { isScopedCredentialGroupsAvailable } from '@/lib/credential-groups/scoped-availability' +import { requireResourcePolicy } from '@/lib/resource-policies/repository' + +/** Applies organization grants after the calling application operation authorizes workspace access. */ +export async function filterWorkspaceAccountCredentials< + T extends { id: string; type: string; providerId: string }, +>(context: WorkspaceAuthorizationContext, credentials: T[]): Promise { + const managedIds = credentials + .filter((entry) => entry.type === 'managed_oauth' || entry.type === 'personal_token') + .map((entry) => entry.id) + if (!managedIds.length) return credentials + const bindings = await db + .select({ + id: credential.id, + organizationId: credential.organizationId, + workspaceId: credential.workspaceId, + groupId: credentialGroup.id, + groupOrganizationId: credentialGroup.organizationId, + groupWorkspaceId: credentialGroup.workspaceId, + providerId: credential.providerId, + type: credential.type, + }) + .from(credential) + .innerJoin( + credentialGroupEnrollment, + eq(credentialGroupEnrollment.id, credential.credentialGroupEnrollmentId) + ) + .innerJoin(credentialGroup, eq(credentialGroup.id, credentialGroupEnrollment.credentialGroupId)) + .where(inArray(credential.id, managedIds)) + for (const binding of bindings) { + if ( + !sameResourceScope( + resourceScopeFromOwner(binding), + resourceScopeFromOwner({ + organizationId: binding.groupOrganizationId, + workspaceId: binding.groupWorkspaceId, + }) + ) + ) + throw new Error('Credential and enrollment group owners do not match') + } + const byId = new Map(bindings.map((binding) => [binding.id, binding])) + const policies = new Map() + const organizationId = context.workspaceOrganizationId + const organizationAvailable = + organizationId && bindings.some((binding) => binding.organizationId === organizationId) + ? await isScopedCredentialGroupsAvailable({ kind: 'organization', organizationId }) + : false + for (const binding of bindings) { + if ( + !binding.organizationId || + binding.organizationId !== organizationId || + !organizationAvailable || + policies.has(binding.groupId) + ) + continue + const policy = await requireResourcePolicy({ + organizationId: binding.organizationId, + resourceType: 'credential_group', + resourceId: binding.groupId, + codec: organizationAccountAccessPolicyCodec, + }) + policies.set(binding.groupId, policy.document) + } + return credentials.filter((entry) => { + if (entry.type !== 'managed_oauth' && entry.type !== 'personal_token') return true + const binding = byId.get(entry.id) + if (!binding) return false + if (!binding.organizationId) return binding.workspaceId === context.workspaceId + if (binding.organizationId !== organizationId || !organizationAvailable) return false + const policy = policies.get(binding.groupId) + if (!policy) throw new Error('Organization credential policy was not loaded') + if ( + !binding.providerId || + binding.providerId !== entry.providerId || + binding.type !== entry.type + ) + throw new Error('Credential binding changed while listing accounts') + if (binding.type === 'personal_token' && binding.providerId !== 'gitlab') + throw new Error('Unsupported personal-token provider') + const type = + binding.type === 'personal_token' + ? 'personal_token:gitlab' + : organizationOAuthCredentialType(binding.providerId) + return organizationAccountPolicyAllowsWorkspace(policy, context.workspaceId, type) + }) +} diff --git a/apps/sim/lib/credentials/managed-mcp.ts b/apps/sim/lib/credentials/managed-mcp.ts index df621ae31c7..597a93e94db 100644 --- a/apps/sim/lib/credentials/managed-mcp.ts +++ b/apps/sim/lib/credentials/managed-mcp.ts @@ -20,6 +20,7 @@ import { sameResourceScopeCondition, } from '@/lib/core/resource-scope.server' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' +import type { OrganizationCredentialType } from '@/lib/credential-groups/credential-types' import { lockCredentialGroupEnrollmentLifecycle } from '@/lib/credential-groups/enrollments' import { getManagedMcpConnector } from '@/lib/credential-groups/managed-mcp-connectors' import { isScopedCredentialGroupsAvailable } from '@/lib/credential-groups/scoped-availability' @@ -36,6 +37,7 @@ interface ManagedMcpTokenEnvelope { } export interface ManagedMcpCredentialApplicationContext extends WorkspaceAuthorizationContext { + credentialType: OrganizationCredentialType organizationId?: string credentialId: string credentialGroupId: string @@ -45,6 +47,7 @@ export interface ManagedMcpCredentialApplicationContext extends WorkspaceAuthori } export interface ManagedMcpRuntimeCredential { + credentialType: OrganizationCredentialType grantedAt: Date oauthConfigVersion: number scope: ResourceScope @@ -150,7 +153,7 @@ export async function loadManagedMcpCredentialApplicationContext( if (!row.managedConnectorId) { throw new Error(`Managed MCP server ${row.mcpServerId} has no connector ID`) } - getManagedMcpConnector(row.managedConnectorId) + const connector = getManagedMcpConnector(row.managedConnectorId) const workspaceContext = await loadActiveWorkspaceApplicationContext(workspaceId) if ( !workspaceContext || @@ -159,7 +162,12 @@ export async function loadManagedMcpCredentialApplicationContext( : row.workspaceId !== workspaceId) ) return null - return { ...row, ...workspaceContext, organizationId: row.organizationId ?? undefined } + return { + ...row, + ...workspaceContext, + credentialType: `mcp:${connector.id}` as const, + organizationId: row.organizationId ?? undefined, + } } export async function loadManagedMcpRuntimeCredential( @@ -221,7 +229,7 @@ export async function loadManagedMcpRuntimeCredential( if (!row.managedConnectorId) { throw new ManagedMcpCredentialError('Managed MCP connector metadata is missing', 500) } - getManagedMcpConnector(row.managedConnectorId) + const connector = getManagedMcpConnector(row.managedConnectorId) if ( row.status !== 'active' || row.groupStatus !== 'active' || @@ -239,6 +247,7 @@ export async function loadManagedMcpRuntimeCredential( if (!row.grantedAt) throw new ManagedMcpCredentialError('Managed MCP grant version is missing', 500) return { + credentialType: `mcp:${connector.id}`, credentialId: row.credentialId, oauthConfigVersion: row.serverOauthConfigVersion, credentialGroupId: row.credentialGroupId, diff --git a/apps/sim/lib/credentials/managed-oauth.ts b/apps/sim/lib/credentials/managed-oauth.ts index 57a4cc1998b..51429749b89 100644 --- a/apps/sim/lib/credentials/managed-oauth.ts +++ b/apps/sim/lib/credentials/managed-oauth.ts @@ -11,6 +11,10 @@ import { } from '@/lib/core/resource-scope' import { resourceScopeCondition } from '@/lib/core/resource-scope.server' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' +import { + type OrganizationCredentialType, + organizationOAuthCredentialType, +} from '@/lib/credential-groups/credential-types' import { type CredentialGroupProviderAdapter, CredentialGroupProviderConfigurationError, @@ -73,6 +77,7 @@ interface ResolveManagedOAuthTokenParams { } export interface ManagedOAuthCredentialApplicationContext extends WorkspaceAuthorizationContext { + credentialType: OrganizationCredentialType organizationId?: string credentialId: string credentialGroupId: string @@ -196,9 +201,11 @@ export async function loadManagedOAuthCredentialApplicationContext( : row.workspaceId !== workspaceId ) return null + if (!row.providerId) throw new Error('Managed OAuth credential is missing its provider') return { ...workspaceContext, ...(row.organizationId ? { organizationId: row.organizationId } : {}), + credentialType: organizationOAuthCredentialType(row.providerId), credentialId: row.id, credentialGroupId: row.credentialGroupId, credentialGroupEnrollmentId: row.credentialGroupEnrollmentId, diff --git a/apps/sim/lib/credentials/personal-tokens.ts b/apps/sim/lib/credentials/personal-tokens.ts index 41cbd2b2ba3..00f300f9751 100644 --- a/apps/sim/lib/credentials/personal-tokens.ts +++ b/apps/sim/lib/credentials/personal-tokens.ts @@ -120,7 +120,7 @@ export async function requirePersonalTokenEnrollment( input: ResourceOwner & { userId: string; enrollmentId: string | null }, executor: DbOrTx = db, lock = false -): Promise { +): Promise<{ credentialGroupId: string }> { const scope = resourceScopeFromOwner(input) if (!input.enrollmentId) throw new OrchestrationError( @@ -173,6 +173,7 @@ export async function requirePersonalTokenEnrollment( executor ) } + return { credentialGroupId: binding.credentialGroupId } } export interface CreatePersonalTokenParams { diff --git a/apps/sim/lib/mcp/application/managed-auth-provider.ts b/apps/sim/lib/mcp/application/managed-auth-provider.ts index cf631231024..93b7dc1e062 100644 --- a/apps/sim/lib/mcp/application/managed-auth-provider.ts +++ b/apps/sim/lib/mcp/application/managed-auth-provider.ts @@ -15,12 +15,15 @@ export async function loadManagedMcpAuthProvider( ): Promise { const current = await loadManagedMcpRuntimeCredential(credentialId, workspaceId) if (current.scope.kind === 'organization') { - await requireOrganizationAccountsWorkspaceAccess({ - workspaceId, - workspaceOrganizationId: current.scope.organizationId, - organizationId: current.scope.organizationId, - credentialGroupId: current.credentialGroupId, - }) + await requireOrganizationAccountsWorkspaceAccess( + { + workspaceId, + workspaceOrganizationId: current.scope.organizationId, + organizationId: current.scope.organizationId, + credentialGroupId: current.credentialGroupId, + }, + current.credentialType + ) } const clientRow = await getOrCreateOauthRow({ mcpServerId: current.mcpServerId, diff --git a/apps/sim/lib/mcp/application/managed-connections.test.ts b/apps/sim/lib/mcp/application/managed-connections.test.ts index b3b22e831ea..9a85b92e116 100644 --- a/apps/sim/lib/mcp/application/managed-connections.test.ts +++ b/apps/sim/lib/mcp/application/managed-connections.test.ts @@ -30,6 +30,7 @@ vi.mock('@sim/platform-authz/workspace', () => ({ resolveEffectiveWorkspacePermission: mocks.permission, })) +import { buildOrganizationAccountAccessPolicy } from '@/lib/credential-groups/application/workspace-access-policy' import { listManagedMcpConnectionsUseCase } from '@/lib/mcp/application/managed-connections' const principal: SessionPrincipal = { kind: 'session', userId: 'user-1', sessionId: 'session-1' } @@ -60,7 +61,11 @@ describe('managed MCP connection catalog', () => { billedAccountUserId: 'owner-1', }) mocks.permission.mockResolvedValue('read') - mocks.requireAccess.mockResolvedValue(undefined) + mocks.requireAccess.mockResolvedValue( + buildOrganizationAccountAccessPolicy('group-1', [ + { workspaceId: 'workspace-1', access: { mode: 'all' } }, + ]) + ) }) it('uses organization ownership and workspace access before exposing credential operations', async () => { diff --git a/apps/sim/lib/mcp/application/managed-connections.ts b/apps/sim/lib/mcp/application/managed-connections.ts index 19f7815f2a4..c3e26b82102 100644 --- a/apps/sim/lib/mcp/application/managed-connections.ts +++ b/apps/sim/lib/mcp/application/managed-connections.ts @@ -4,9 +4,13 @@ import { and, asc, eq, inArray, isNotNull, isNull, or, sql } from 'drizzle-orm' import { getWorkspaceOwnerSubscriptionAccess } from '@/lib/billing/core/workspace-access' import { defineAuthorizedWorkspaceUseCase } from '@/lib/core/application' import { requireOrganizationAccountsWorkspaceAccess } from '@/lib/credential-groups/application/organization-workspace-access' +import { organizationAccountPolicyAllowsWorkspace } from '@/lib/credential-groups/application/workspace-access-policy' import { isCredentialGroupsAvailable } from '@/lib/credential-groups/availability' import { loadScopedAccountsCredentialListContext } from '@/lib/credential-groups/credentials' -import { getManagedMcpConnector } from '@/lib/credential-groups/managed-mcp-connectors' +import { + getManagedMcpConnector, + MANAGED_MCP_CONNECTOR_IDS, +} from '@/lib/credential-groups/managed-mcp-connectors' import { resolveMcpWorkspaceContext } from '@/lib/mcp/application/context' import { mcpServerOperations } from '@/lib/mcp/application/operations' import type { McpToolSchema } from '@/lib/mcp/types' @@ -48,14 +52,19 @@ export const listManagedMcpConnectionsUseCase = defineAuthorizedWorkspaceUseCase organizationId, }) if (!group) return { servers: [], tools: [] } - await requireOrganizationAccountsWorkspaceAccess({ + const policy = await requireOrganizationAccountsWorkspaceAccess({ ...context, organizationId, credentialGroupId: group.credentialGroupId, }) + const allowedConnectorIds = MANAGED_MCP_CONNECTOR_IDS.filter((id) => + organizationAccountPolicyAllowsWorkspace(policy, context.workspaceId, `mcp:${id}`) + ) + if (!allowedConnectorIds.length) return { servers: [], tools: [] } const managedCatalogScope = () => and( eq(credential.organizationId, organizationId), + inArray(mcpServers.managedConnectorId, allowedConnectorIds), eq(credentialGroup.id, group.credentialGroupId), eq(credential.mcpOauthConfigVersion, mcpServers.oauthConfigVersion), eq(credential.type, 'managed_mcp'), diff --git a/apps/sim/lib/resource-policies/conditions/credential-type.ts b/apps/sim/lib/resource-policies/conditions/credential-type.ts new file mode 100644 index 00000000000..6ec768392a7 --- /dev/null +++ b/apps/sim/lib/resource-policies/conditions/credential-type.ts @@ -0,0 +1,13 @@ +import { defineResourcePolicyCondition } from '@/lib/resource-policies/conditions/types' + +export const CREDENTIAL_TYPE_CONDITION_KEY = 'credential_group:CredentialType' as const + +/** Resolves the integration from the canonical credential, independently of caller input. */ +export const credentialTypeConditionDefinition = defineResourcePolicyCondition({ + key: CREDENTIAL_TYPE_CONDITION_KEY, + label: 'Credential type', + valueType: 'string', + operators: ['StringEquals'], + selector: { type: 'internal' }, + resolve: (facts) => facts.credentialType, +}) diff --git a/apps/sim/lib/resource-policies/conditions/registry.ts b/apps/sim/lib/resource-policies/conditions/registry.ts index 780dfded096..640a02936db 100644 --- a/apps/sim/lib/resource-policies/conditions/registry.ts +++ b/apps/sim/lib/resource-policies/conditions/registry.ts @@ -1,5 +1,6 @@ import { credentialGroupActorOwnsCredentialConditionDefinition } from '@/lib/resource-policies/conditions/credential-group-actor-owns-credential' import { credentialGroupOptionIdConditionDefinition } from '@/lib/resource-policies/conditions/credential-group-option' +import { credentialTypeConditionDefinition } from '@/lib/resource-policies/conditions/credential-type' import type { ResourcePolicyConditionDefinition, ResourcePolicyConditionKey, @@ -9,6 +10,7 @@ import { workflowModeResourcePolicyConditionDefinition } from '@/lib/resource-po export const RESOURCE_POLICY_CONDITION_DEFINITIONS = Object.freeze({ 'credential_group:ActorOwnsCredential': credentialGroupActorOwnsCredentialConditionDefinition, 'credential_group:OptionId': credentialGroupOptionIdConditionDefinition, + 'credential_group:CredentialType': credentialTypeConditionDefinition, 'execution:WorkflowMode': workflowModeResourcePolicyConditionDefinition, } as const satisfies Record) diff --git a/apps/sim/lib/resource-policies/conditions/types.ts b/apps/sim/lib/resource-policies/conditions/types.ts index d22c9397071..c2c9eb79974 100644 --- a/apps/sim/lib/resource-policies/conditions/types.ts +++ b/apps/sim/lib/resource-policies/conditions/types.ts @@ -3,6 +3,7 @@ export const RESOURCE_POLICY_CONDITION_OPERATORS = ['Bool', 'StringEquals'] as c export type ResourcePolicyConditionOperator = (typeof RESOURCE_POLICY_CONDITION_OPERATORS)[number] export interface ResourcePolicyConditionEvaluationFacts { + credentialType?: string credentialGroupActorEnrollmentId?: string credentialGroupCredentialEnrollmentId?: string /** The option the credential being accessed was collected under. */ @@ -36,6 +37,7 @@ export interface ResourcePolicyConditionDefinition { export type ResourcePolicyConditionKey = | 'credential_group:ActorOwnsCredential' | 'credential_group:OptionId' + | 'credential_group:CredentialType' | 'execution:WorkflowMode' export function defineResourcePolicyCondition( diff --git a/apps/sim/lib/resource-policies/registry.ts b/apps/sim/lib/resource-policies/registry.ts index 25070bcd4ab..32c42363bdc 100644 --- a/apps/sim/lib/resource-policies/registry.ts +++ b/apps/sim/lib/resource-policies/registry.ts @@ -21,6 +21,7 @@ export const RESOURCE_POLICY_DEFINITIONS = Object.freeze({ conditionKeys: [ 'credential_group:ActorOwnsCredential', 'credential_group:OptionId', + 'credential_group:CredentialType', 'execution:WorkflowMode', ], }, diff --git a/apps/sim/lib/settings/application/organization-section-access.test.ts b/apps/sim/lib/settings/application/organization-section-access.test.ts index f425f2c3604..93602fa53a8 100644 --- a/apps/sim/lib/settings/application/organization-section-access.test.ts +++ b/apps/sim/lib/settings/application/organization-section-access.test.ts @@ -60,7 +60,7 @@ describe('organization settings authorization', () => { it.each([ { groups: false, search: false, connectedAccounts: false, integrations: false }, { groups: true, search: false, connectedAccounts: true, integrations: false }, - { groups: true, search: true, connectedAccounts: false, integrations: true }, + { groups: true, search: true, connectedAccounts: true, integrations: true }, ])( 'selects the setup page with groups=$groups and search=$search', async ({ groups, search, connectedAccounts, integrations }) => { @@ -92,7 +92,7 @@ describe('organization settings authorization', () => { } ) - it('propagates Search availability failures instead of selecting the old UI', async () => { + it('keeps Credential Groups independent of Search availability', async () => { mocks.search.mockRejectedValue(new Error('Feature configuration unavailable')) await expect( authorizeOrganizationSettingsSection({ @@ -100,7 +100,8 @@ describe('organization settings authorization', () => { userId: 'admin', section: 'connected-accounts', }) - ).rejects.toThrow('Feature configuration unavailable') + ).resolves.toBe(true) + expect(mocks.search).not.toHaveBeenCalled() }) it('checks current target organization membership before billing reads', async () => { diff --git a/apps/sim/lib/settings/application/organization-section-access.ts b/apps/sim/lib/settings/application/organization-section-access.ts index bf762ea9e8a..7427638a686 100644 --- a/apps/sim/lib/settings/application/organization-section-access.ts +++ b/apps/sim/lib/settings/application/organization-section-access.ts @@ -24,9 +24,7 @@ export async function authorizeOrganizationSettingsSection({ if (!(await canOpenOrganizationSettingsSection(organizationId, userId, section))) return false if (section === 'connected-accounts') { - if (!(await isScopedCredentialGroupsAvailable({ kind: 'organization', organizationId }))) - return false - return !(await isKnowledgeMemberAccessAvailable({ organizationId })) + return isScopedCredentialGroupsAvailable({ kind: 'organization', organizationId }) } if (section === 'search-mcp' || section === 'search-slack' || section === 'integrations') return isKnowledgeMemberAccessAvailable({ organizationId }) diff --git a/apps/sim/lib/settings/application/workspace-section-access.test.ts b/apps/sim/lib/settings/application/workspace-section-access.test.ts index cb9724ac59e..248d85372c1 100644 --- a/apps/sim/lib/settings/application/workspace-section-access.test.ts +++ b/apps/sim/lib/settings/application/workspace-section-access.test.ts @@ -336,10 +336,10 @@ describe('authorizeWorkspaceSettingsSection', () => { it.each([ { groups: true, search: false, allowed: true }, { groups: false, search: false, allowed: false }, - { groups: true, search: true, allowed: false }, + { groups: true, search: true, allowed: true }, { groups: false, search: true, allowed: false }, ])( - 'gates Connected accounts with organization groups=$groups and search=$search', + 'gates Credential Groups with organization groups=$groups and search=$search', async ({ groups, search, allowed }) => { mocks.checkWorkspaceAccess.mockResolvedValue(ORGANIZATION_ACCESS) mocks.isScopedCredentialGroupsAvailable.mockResolvedValue(groups) @@ -357,11 +357,7 @@ describe('authorizeWorkspaceSettingsSection', () => { kind: 'organization', organizationId: 'organization-1', }) - if (groups) { - expect(mocks.isKnowledgeMemberAccessAvailable).toHaveBeenCalledWith({ - organizationId: 'organization-1', - }) - } + expect(mocks.isKnowledgeMemberAccessAvailable).not.toHaveBeenCalled() expect(mocks.isOrganizationOnEnterprisePlan).not.toHaveBeenCalled() } ) @@ -385,9 +381,9 @@ describe('authorizeWorkspaceSettingsSection', () => { expect(mocks.canOpenOrganizationSettingsSection).not.toHaveBeenCalled() }) - it('propagates feature lookup failures instead of opening Connected accounts', async () => { + it('propagates feature lookup failures instead of opening Credential Groups', async () => { mocks.checkWorkspaceAccess.mockResolvedValue(ORGANIZATION_ACCESS) - mocks.isKnowledgeMemberAccessAvailable.mockRejectedValue(new Error('Feature lookup failed')) + mocks.isScopedCredentialGroupsAvailable.mockRejectedValue(new Error('Feature lookup failed')) await expect(authorize('connected-accounts')).rejects.toThrow('Feature lookup failed') }) diff --git a/packages/db/credential-group-resource-policies.ts b/packages/db/credential-group-resource-policies.ts index 28d70c986fd..c21292ed4fb 100644 --- a/packages/db/credential-group-resource-policies.ts +++ b/packages/db/credential-group-resource-policies.ts @@ -370,41 +370,76 @@ export function validateOrganizationAccountPolicyDocument( requireCanonicalId(resource.id, 'Organization account resource ID') !== expectedResourceId ) throw new Error('Organization account policy resource does not match its canonical resource') - if (!Array.isArray(document.statements) || document.statements.length > 1) - throw new Error('Organization account policy supports only workspace access') - if (document.statements.length === 0) return - const statement = requireRecord( - document.statements[0], - 'Organization account workspace statement' - ) - requireExactKeys( - statement, - ['sid', 'effect', 'actions', 'principals'], - 'Organization account workspace statement' - ) - if ( - statement.sid !== 'WorkspaceCredentialAccess' || - statement.effect !== 'allow' || - !Array.isArray(statement.actions) || - statement.actions.length !== 1 || - statement.actions[0] !== CREDENTIAL_USE_ACTION - ) - throw new Error('Organization account workspace statement is invalid') - if ( - !Array.isArray(statement.principals) || - statement.principals.length < 1 || - statement.principals.length > 1000 - ) - throw new Error('Organization account policy supports 1-1000 workspaces') - let previous = '' - for (const value of statement.principals) { - const principal = requireRecord(value, 'Organization account workspace principal') - requireExactKeys(principal, ['type', 'workspaceId'], 'Organization account workspace principal') - const id = requireCanonicalId(principal.workspaceId, 'Organization account workspace ID') - if (principal.type !== 'workspace' || id <= previous) - throw new Error('Organization account workspace principals must be unique and sorted') - previous = id + if (!Array.isArray(document.statements) || document.statements.length > 128) + throw new Error('Organization account policy has too many statements') + const seenStatements = new Set() + const allWorkspaces = new Set() + const selectedWorkspaces = new Set() + for (const value of document.statements) { + const statement = requireRecord(value, 'Organization account workspace statement') + requireExactKeys( + statement, + [ + 'sid', + 'effect', + 'actions', + 'principals', + ...(statement.condition === undefined ? [] : ['condition']), + ], + 'Organization account workspace statement' + ) + let credentialType: string | undefined + if (statement.condition !== undefined) { + const condition = requireRecord(statement.condition, 'Credential type condition') + requireExactKeys(condition, ['StringEquals'], 'Credential type condition') + const equals = requireRecord(condition.StringEquals, 'Credential type StringEquals') + requireExactKeys(equals, ['credential_group:CredentialType'], 'Credential type StringEquals') + credentialType = requireCanonicalId( + equals['credential_group:CredentialType'], + 'Credential type' + ) + if (!/^(oauth|mcp|personal_token):[a-z][a-z0-9-]*$/.test(credentialType)) + throw new Error('Invalid credential type') + } + const sid = credentialType + ? `WorkspaceCredentialAccess:${credentialType}` + : 'WorkspaceCredentialAccess' + if ( + statement.sid !== sid || + seenStatements.has(sid) || + statement.effect !== 'allow' || + !Array.isArray(statement.actions) || + statement.actions.length !== 1 || + statement.actions[0] !== CREDENTIAL_USE_ACTION + ) + throw new Error('Organization account workspace statement is invalid') + seenStatements.add(sid) + if ( + !Array.isArray(statement.principals) || + statement.principals.length < 1 || + statement.principals.length > 1000 + ) + throw new Error('Organization account policy supports 1-1000 workspaces') + let previous = '' + for (const value of statement.principals) { + const principal = requireRecord(value, 'Organization account workspace principal') + requireExactKeys( + principal, + ['type', 'workspaceId'], + 'Organization account workspace principal' + ) + const id = requireCanonicalId(principal.workspaceId, 'Organization account workspace ID') + if (principal.type !== 'workspace' || id <= previous) + throw new Error('Organization account workspace principals must be unique and sorted') + previous = id + const workspaces = credentialType ? selectedWorkspaces : allWorkspaces + workspaces.add(id) + } } + if ([...allWorkspaces].some((id) => selectedWorkspaces.has(id))) + throw new Error('Workspace has overlapping all and selected grants') + if (new Set([...allWorkspaces, ...selectedWorkspaces]).size > 1000) + throw new Error('Organization account policy supports at most 1000 workspaces') } function assertPage( diff --git a/packages/db/script-migrations/0010_backfill_credential_group_resource_policies.test.ts b/packages/db/script-migrations/0010_backfill_credential_group_resource_policies.test.ts index b412333b8d3..94d45698e92 100644 --- a/packages/db/script-migrations/0010_backfill_credential_group_resource_policies.test.ts +++ b/packages/db/script-migrations/0010_backfill_credential_group_resource_policies.test.ts @@ -381,6 +381,46 @@ describe('organization account policy validation', () => { ] : [], }) + it('accepts integration conditions without rewriting organization policy grants', () => { + const statement = policy(['a']).statements[0] + const typed = { + ...policy([]), + statements: [ + { + ...statement, + sid: 'WorkspaceCredentialAccess:oauth:gmail', + condition: { StringEquals: { 'credential_group:CredentialType': 'oauth:gmail' } }, + }, + ], + } + expect(() => validateOrganizationAccountPolicyDocument(typed, 'group-org')).not.toThrow() + expect(() => + validateOrganizationAccountPolicyDocument( + { ...typed, statements: [...typed.statements, statement] }, + 'group-org' + ) + ).toThrow('overlapping') + expect(() => + validateOrganizationAccountPolicyDocument( + { ...typed, statements: [...typed.statements, ...typed.statements] }, + 'group-org' + ) + ).toThrow('invalid') + expect(() => + validateOrganizationAccountPolicyDocument( + { + ...typed, + statements: [ + { + ...typed.statements[0], + condition: { StringNotEquals: { 'credential_group:CredentialType': 'oauth:gmail' } }, + }, + ], + }, + 'group-org' + ) + ).toThrow() + }) it('accepts deny-by-default and the maximum workspace allowlist', () => { expect(() => validateOrganizationAccountPolicyDocument(policy([]), 'group-org')).not.toThrow() expect(() => diff --git a/scripts/check-tool-registry-boundary.baseline.json b/scripts/check-tool-registry-boundary.baseline.json index 7f6ee38c1af..e2ca0a52ad2 100644 --- a/scripts/check-tool-registry-boundary.baseline.json +++ b/scripts/check-tool-registry-boundary.baseline.json @@ -6,68 +6,68 @@ }, "entries": { "app/api/v2/blocks/[blockId]/route.ts": { - "modules": 1600, + "modules": 1633, "gateways": { "apps/sim/triggers/index.ts": 487, + "apps/sim/lib/api/server/routes/index.ts": 486, "apps/sim/triggers/registry.ts": 485, - "apps/sim/lib/api/server/routes/index.ts": 461, - "apps/sim/lib/api/server/routes/internal-json-route.ts": 409, - "apps/sim/lib/auth/index.ts": 396, - "apps/sim/blocks/registry.ts": 360, - "apps/sim/lib/webhooks/providers/index.ts": 118, - "apps/sim/lib/webhooks/providers/registry.ts": 116 + "apps/sim/lib/api/server/routes/internal-json-route.ts": 431, + "apps/sim/lib/auth/index.ts": 418, + "apps/sim/blocks/registry.ts": 359, + "apps/sim/lib/webhooks/providers/index.ts": 117, + "apps/sim/lib/webhooks/providers/registry.ts": 115 } }, "app/api/v2/blocks/route.ts": { - "modules": 1599, + "modules": 1632, "gateways": { "apps/sim/triggers/index.ts": 487, "apps/sim/triggers/registry.ts": 485, - "apps/sim/lib/api/server/routes/index.ts": 455, - "apps/sim/lib/api/server/routes/internal-json-route.ts": 412, - "apps/sim/lib/auth/index.ts": 399, - "apps/sim/blocks/registry.ts": 360, - "apps/sim/lib/webhooks/providers/index.ts": 119, - "apps/sim/lib/webhooks/providers/registry.ts": 116 + "apps/sim/lib/api/server/routes/index.ts": 480, + "apps/sim/lib/api/server/routes/internal-json-route.ts": 434, + "apps/sim/lib/auth/index.ts": 421, + "apps/sim/blocks/registry.ts": 359, + "apps/sim/lib/webhooks/providers/index.ts": 118, + "apps/sim/lib/webhooks/providers/registry.ts": 115 } }, "app/api/v2/connector-types/route.ts": { - "modules": 1664, + "modules": 1697, "gateways": { + "apps/sim/lib/api/server/routes/index.ts": 488, "apps/sim/triggers/index.ts": 487, "apps/sim/triggers/registry.ts": 485, - "apps/sim/lib/api/server/routes/index.ts": 463, - "apps/sim/lib/api/server/routes/internal-json-route.ts": 411, - "apps/sim/lib/auth/index.ts": 398, + "apps/sim/lib/api/server/routes/internal-json-route.ts": 433, + "apps/sim/lib/auth/index.ts": 420, "apps/sim/blocks/registry.ts": 360, - "apps/sim/lib/webhooks/providers/index.ts": 119, - "apps/sim/lib/webhooks/providers/registry.ts": 116 + "apps/sim/lib/webhooks/providers/index.ts": 118, + "apps/sim/lib/webhooks/providers/registry.ts": 115 } }, "app/api/v2/tools/[toolId]/route.ts": { - "modules": 1597, + "modules": 1630, "gateways": { + "apps/sim/lib/api/server/routes/index.ts": 487, "apps/sim/triggers/index.ts": 487, "apps/sim/triggers/registry.ts": 485, - "apps/sim/lib/api/server/routes/index.ts": 462, - "apps/sim/lib/api/server/routes/internal-json-route.ts": 410, - "apps/sim/lib/auth/index.ts": 397, + "apps/sim/lib/api/server/routes/internal-json-route.ts": 432, + "apps/sim/lib/auth/index.ts": 419, "apps/sim/blocks/registry.ts": 360, - "apps/sim/lib/webhooks/providers/index.ts": 119, - "apps/sim/lib/webhooks/providers/registry.ts": 116 + "apps/sim/lib/webhooks/providers/index.ts": 118, + "apps/sim/lib/webhooks/providers/registry.ts": 115 } }, "app/api/v2/tools/route.ts": { - "modules": 1598, + "modules": 1631, "gateways": { "apps/sim/triggers/index.ts": 487, "apps/sim/triggers/registry.ts": 485, - "apps/sim/lib/api/server/routes/index.ts": 453, - "apps/sim/lib/api/server/routes/internal-json-route.ts": 410, - "apps/sim/lib/auth/index.ts": 397, + "apps/sim/lib/api/server/routes/index.ts": 478, + "apps/sim/lib/api/server/routes/internal-json-route.ts": 432, + "apps/sim/lib/auth/index.ts": 419, "apps/sim/blocks/registry.ts": 360, - "apps/sim/lib/webhooks/providers/index.ts": 119, - "apps/sim/lib/webhooks/providers/registry.ts": 116 + "apps/sim/lib/webhooks/providers/index.ts": 118, + "apps/sim/lib/webhooks/providers/registry.ts": 115 } }, "app/workspace/[workspaceId]/access-requests/loading.tsx": { @@ -75,17 +75,17 @@ "gateways": {} }, "app/workspace/[workspaceId]/access-requests/page.tsx": { - "modules": 44, + "modules": 45, "gateways": { - "apps/sim/components/access-requests/my-access-requests.tsx": 42 + "apps/sim/components/access-requests/my-access-requests.tsx": 43 } }, "app/workspace/[workspaceId]/chat/[chatId]/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/chat/[chatId]/layout.tsx": { @@ -93,89 +93,89 @@ "gateways": {} }, "app/workspace/[workspaceId]/chat/[chatId]/page.tsx": { - "modules": 3068, + "modules": 3125, "gateways": { - "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1498, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx": 910, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/index.ts": 743, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/index.ts": 740, + "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1521, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx": 961, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/index.ts": 795, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/index.ts": 790, "apps/sim/triggers/registry.ts": 485, "apps/sim/blocks/registry.ts": 330, - "apps/sim/lib/auth/index.ts": 267, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 201 + "apps/sim/lib/auth/index.ts": 271, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 200 } }, "app/workspace/[workspaceId]/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/files/[fileId]/loading.tsx": { - "modules": 159, + "modules": 154, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/files/[fileId]/page.tsx": { - "modules": 2176, + "modules": 2186, "gateways": { "apps/sim/triggers/registry.ts": 485, - "apps/sim/app/workspace/[workspaceId]/files/files.tsx": 411, + "apps/sim/app/workspace/[workspaceId]/files/files.tsx": 417, "apps/sim/blocks/registry.ts": 355, - "apps/sim/lib/auth/index.ts": 274, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/index.ts": 214, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx": 183, - "apps/sim/lib/webhooks/providers/index.ts": 117, - "apps/sim/lib/webhooks/providers/registry.ts": 115 + "apps/sim/lib/auth/index.ts": 276, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/index.ts": 217, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx": 186, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.tsx": 117, + "apps/sim/lib/webhooks/providers/index.ts": 117 } }, "app/workspace/[workspaceId]/files/[fileId]/view/page.tsx": { - "modules": 65, + "modules": 68, "gateways": { - "apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer.tsx": 64, - "apps/sim/hooks/queries/workspace-files.ts": 60 + "apps/sim/app/workspace/[workspaceId]/files/[fileId]/view/file-viewer.tsx": 67, + "apps/sim/hooks/queries/workspace-files.ts": 63 } }, "app/workspace/[workspaceId]/files/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/files/loading.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/files/page.tsx": { - "modules": 2175, + "modules": 2185, "gateways": { "apps/sim/triggers/registry.ts": 485, - "apps/sim/app/workspace/[workspaceId]/files/files.tsx": 412, + "apps/sim/app/workspace/[workspaceId]/files/files.tsx": 418, "apps/sim/blocks/registry.ts": 355, - "apps/sim/lib/auth/index.ts": 274, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/index.ts": 214, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx": 183, - "apps/sim/lib/webhooks/providers/index.ts": 117, - "apps/sim/lib/webhooks/providers/registry.ts": 115 + "apps/sim/lib/auth/index.ts": 276, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/index.ts": 217, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx": 186, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.tsx": 117, + "apps/sim/lib/webhooks/providers/index.ts": 117 } }, "app/workspace/[workspaceId]/home/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/home/layout.tsx": { @@ -183,224 +183,203 @@ "gateways": {} }, "app/workspace/[workspaceId]/home/page.tsx": { - "modules": 3068, + "modules": 3125, "gateways": { - "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1498, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx": 910, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/index.ts": 743, - "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/index.ts": 740, + "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1521, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx": 961, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/index.ts": 795, + "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/index.ts": 790, "apps/sim/triggers/registry.ts": 485, "apps/sim/blocks/registry.ts": 330, - "apps/sim/lib/auth/index.ts": 267, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 201 + "apps/sim/lib/auth/index.ts": 271, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 200 } }, "app/workspace/[workspaceId]/integrations/[block]/page.tsx": { - "modules": 1166, + "modules": 1189, "gateways": { - "apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx": 1133, + "apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx": 1115, "apps/sim/triggers/index.ts": 524, "apps/sim/triggers/registry.ts": 522, "apps/sim/blocks/registry.ts": 367, - "apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-skills-section.tsx": 63, - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 59, - "apps/sim/lib/api/contracts/index.ts": 34, + "apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-skills-section.tsx": 62, + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 58, + "apps/sim/lib/api/contracts/index.ts": 33, "apps/sim/triggers/clickup/index.ts": 32 } }, "app/workspace/[workspaceId]/integrations/connected/[credentialId]/page.tsx": { - "modules": 1213, + "modules": 1214, "gateways": { - "apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx": 1103, + "apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx": 1173, "apps/sim/triggers/registry.ts": 522, "apps/sim/blocks/registry.ts": 373, "apps/sim/app/workspace/[workspaceId]/components/credential-detail/index.ts": 102, "apps/sim/components/permissions/index.ts": 89, "apps/sim/components/permissions/add-people-modal.tsx": 80, "apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx": 78, - "apps/sim/lib/api/contracts/index.ts": 40 + "apps/sim/lib/api/contracts/index.ts": 39 } }, "app/workspace/[workspaceId]/integrations/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/integrations/page.tsx": { - "modules": 1140, + "modules": 1164, "gateways": { - "apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx": 983, + "apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx": 1012, "apps/sim/blocks/registry.ts": 895, "apps/sim/triggers/index.ts": 524, "apps/sim/triggers/registry.ts": 522, - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 60, - "apps/sim/lib/api/contracts/index.ts": 34, + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 59, + "apps/sim/lib/api/contracts/index.ts": 33, "apps/sim/triggers/clickup/index.ts": 32 } }, "app/workspace/[workspaceId]/knowledge/[id]/[documentId]/loading.tsx": { - "modules": 159, + "modules": 154, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/knowledge/[id]/[documentId]/page.tsx": { - "modules": 1368, + "modules": 1379, "gateways": { - "apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx": 1208, + "apps/sim/app/workspace/[workspaceId]/knowledge/[id]/[documentId]/document.tsx": 1198, "apps/sim/triggers/registry.ts": 522, - "apps/sim/blocks/registry.ts": 362, - "apps/sim/blocks/registry-maps.ts": 359, - "apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx": 86, + "apps/sim/blocks/registry.ts": 366, + "apps/sim/blocks/registry-maps.ts": 363, + "apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx": 85, "apps/sim/connectors/registry.ts": 67, - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 66, - "apps/sim/lib/api/contracts/index.ts": 33 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 61, + "apps/sim/lib/api/contracts/index.ts": 32 } }, "app/workspace/[workspaceId]/knowledge/[id]/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/knowledge/[id]/loading.tsx": { - "modules": 160, + "modules": 155, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/knowledge/[id]/page.tsx": { - "modules": 1478, + "modules": 1503, "gateways": { - "apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx": 1317, + "apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx": 1321, "apps/sim/triggers/registry.ts": 522, - "apps/sim/blocks/registry.ts": 360, - "apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/index.ts": 93, + "apps/sim/blocks/registry.ts": 361, + "apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/index.ts": 114, "apps/sim/connectors/registry.ts": 67, - "apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx": 60, - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 57, + "apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx": 64, + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 35, "apps/sim/triggers/clickup/index.ts": 32 } }, "app/workspace/[workspaceId]/knowledge/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/knowledge/loading.tsx": { - "modules": 159, + "modules": 154, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/knowledge/page.tsx": { - "modules": 2376, + "modules": 2389, "gateways": { "apps/sim/triggers/registry.ts": 485, "apps/sim/app/workspace/[workspaceId]/knowledge/prefetch.ts": 408, "apps/sim/blocks/registry.ts": 355, - "apps/sim/lib/knowledge/application/knowledge-bases.ts": 347, - "apps/sim/lib/auth/index.ts": 226, - "apps/sim/lib/knowledge/orchestration/index.ts": 211, - "apps/sim/lib/knowledge/orchestration/connectors.ts": 207, - "apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx": 203 + "apps/sim/lib/knowledge/application/knowledge-bases.ts": 346, + "apps/sim/lib/auth/index.ts": 229, + "apps/sim/lib/knowledge/orchestration/index.ts": 210, + "apps/sim/app/workspace/[workspaceId]/knowledge/knowledge.tsx": 209, + "apps/sim/lib/knowledge/orchestration/connectors.ts": 206 } }, "app/workspace/[workspaceId]/layout.tsx": { - "modules": 2302, + "modules": 2309, "gateways": { "apps/sim/triggers/registry.ts": 485, - "apps/sim/lib/auth/index.ts": 402, - "apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx": 387, + "apps/sim/lib/auth/index.ts": 404, + "apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx": 390, "apps/sim/blocks/registry.ts": 352, - "apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/index.ts": 256, + "apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/index.ts": 259, "apps/sim/lib/webhooks/providers/index.ts": 117, "apps/sim/lib/webhooks/providers/registry.ts": 115, "apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/index.ts": 91 } }, "app/workspace/[workspaceId]/logs/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/logs/loading.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/logs/page.tsx": { - "modules": 1729, + "modules": 1744, "gateways": { - "apps/sim/app/workspace/[workspaceId]/logs/logs.tsx": 1571, + "apps/sim/app/workspace/[workspaceId]/logs/logs.tsx": 1591, "apps/sim/triggers/registry.ts": 522, - "apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/execution-snapshot.tsx": 448, - "apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/index.ts": 400, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 394, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 358, + "apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/execution-snapshot/execution-snapshot.tsx": 465, + "apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/index.ts": 417, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 411, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 350, "apps/sim/blocks/registry.ts": 350, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts": 347 + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts": 343 } }, "app/workspace/[workspaceId]/not-found.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/page.tsx": { "modules": 5, "gateways": {} }, - "app/workspace/[workspaceId]/search/error.tsx": { - "modules": 157, - "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 - } - }, - "app/workspace/[workspaceId]/search/page.tsx": { - "modules": 1938, - "gateways": { - "apps/sim/triggers/registry.ts": 485, - "apps/sim/lib/auth/index.ts": 443, - "apps/sim/blocks/registry.ts": 356, - "apps/sim/app/workspace/[workspaceId]/search/search.tsx": 288, - "apps/sim/app/workspace/[workspaceId]/search/components/search-source-setup.tsx": 163, - "apps/sim/lib/webhooks/providers/index.ts": 118, - "apps/sim/lib/webhooks/providers/registry.ts": 116, - "apps/sim/connectors/registry.ts": 67 - } - }, "app/workspace/[workspaceId]/settings/[section]/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/settings/[section]/layout.tsx": { @@ -412,16 +391,16 @@ "gateways": {} }, "app/workspace/[workspaceId]/settings/[section]/page.tsx": { - "modules": 2348, + "modules": 2384, "gateways": { - "apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx": 739, + "apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx": 736, "apps/sim/triggers/registry.ts": 485, - "apps/sim/lib/auth/index.ts": 378, - "apps/sim/blocks/registry.ts": 354, + "apps/sim/lib/auth/index.ts": 382, + "apps/sim/blocks/registry.ts": 355, "apps/sim/lib/webhooks/providers/index.ts": 117, "apps/sim/lib/webhooks/providers/registry.ts": 115, - "apps/sim/app/workspace/[workspaceId]/settings/components/recently-deleted/recently-deleted.tsx": 80, - "apps/sim/ee/access-control/components/access-control.tsx": 75 + "apps/sim/ee/access-control/components/access-control.tsx": 80, + "apps/sim/app/workspace/[workspaceId]/settings/components/recently-deleted/recently-deleted.tsx": 79 } }, "app/workspace/[workspaceId]/settings/billing/credit-usage/layout.tsx": { @@ -433,24 +412,24 @@ "gateways": {} }, "app/workspace/[workspaceId]/settings/billing/credit-usage/page.tsx": { - "modules": 1556, + "modules": 1590, "gateways": { - "apps/sim/lib/auth/index.ts": 1413, + "apps/sim/lib/auth/index.ts": 1443, "apps/sim/triggers/index.ts": 487, "apps/sim/triggers/registry.ts": 485, "apps/sim/blocks/registry.ts": 363, "apps/sim/blocks/registry-maps.ts": 360, - "apps/sim/lib/webhooks/providers/index.ts": 121, - "apps/sim/lib/webhooks/providers/registry.ts": 118, - "apps/sim/lib/workflows/lifecycle.ts": 50 + "apps/sim/lib/webhooks/providers/index.ts": 120, + "apps/sim/lib/webhooks/providers/registry.ts": 117, + "apps/sim/lib/workflows/lifecycle.ts": 52 } }, "app/workspace/[workspaceId]/settings/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/settings/layout.tsx": { @@ -462,12 +441,12 @@ "gateways": {} }, "app/workspace/[workspaceId]/settings/secrets/[credentialId]/loading.tsx": { - "modules": 1141, + "modules": 1146, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/credential-detail/index.ts": 1138, - "apps/sim/components/permissions/index.ts": 1005, - "apps/sim/components/permissions/add-people-modal.tsx": 996, - "apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx": 994, + "apps/sim/app/workspace/[workspaceId]/components/credential-detail/index.ts": 1143, + "apps/sim/components/permissions/index.ts": 1008, + "apps/sim/components/permissions/add-people-modal.tsx": 999, + "apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx": 997, "apps/sim/triggers/registry.ts": 522, "apps/sim/blocks/registry.ts": 375, "apps/sim/blocks/registry-maps.ts": 372, @@ -475,16 +454,16 @@ } }, "app/workspace/[workspaceId]/settings/secrets/[credentialId]/page.tsx": { - "modules": 1226, + "modules": 1225, "gateways": { "apps/sim/triggers/registry.ts": 522, "apps/sim/blocks/registry.ts": 375, "apps/sim/blocks/registry-maps.ts": 372, - "apps/sim/app/workspace/[workspaceId]/components/credential-detail/index.ts": 95, - "apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/secret-detail.tsx": 84, - "apps/sim/components/permissions/index.ts": 84, - "apps/sim/components/permissions/add-people-modal.tsx": 75, - "apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx": 73 + "apps/sim/app/workspace/[workspaceId]/components/credential-detail/index.ts": 99, + "apps/sim/components/permissions/index.ts": 88, + "apps/sim/components/permissions/add-people-modal.tsx": 79, + "apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/secret-detail.tsx": 78, + "apps/sim/app/workspace/[workspaceId]/providers/workspace-permissions-provider.tsx": 77 } }, "app/workspace/[workspaceId]/settings/usage/events/layout.tsx": { @@ -496,9 +475,9 @@ "gateways": {} }, "app/workspace/[workspaceId]/settings/usage/events/page.tsx": { - "modules": 1595, + "modules": 1597, "gateways": { - "apps/sim/lib/auth/index.ts": 1441, + "apps/sim/lib/auth/index.ts": 1443, "apps/sim/triggers/index.ts": 487, "apps/sim/triggers/registry.ts": 485, "apps/sim/blocks/registry.ts": 363, @@ -509,104 +488,104 @@ } }, "app/workspace/[workspaceId]/skills/[skillId]/page.tsx": { - "modules": 1373, + "modules": 1383, "gateways": { - "apps/sim/app/workspace/[workspaceId]/skills/[skillId]/skill-detail.tsx": 1372, + "apps/sim/app/workspace/[workspaceId]/skills/[skillId]/skill-detail.tsx": 1382, "apps/sim/triggers/registry.ts": 522, "apps/sim/blocks/registry.ts": 367, "apps/sim/blocks/registry-maps.ts": 365, - "apps/sim/app/workspace/[workspaceId]/skills/components/skill-fields/index.ts": 161, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-field.tsx": 158, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/index.ts": 80, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/use-markdown-mentions.ts": 78 + "apps/sim/app/workspace/[workspaceId]/skills/components/skill-fields/index.ts": 171, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-field.tsx": 168, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/index.ts": 91, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/use-markdown-mentions.ts": 89 } }, "app/workspace/[workspaceId]/skills/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/skills/new/page.tsx": { - "modules": 1371, + "modules": 1381, "gateways": { - "apps/sim/app/workspace/[workspaceId]/skills/new/skill-create.tsx": 1370, + "apps/sim/app/workspace/[workspaceId]/skills/new/skill-create.tsx": 1380, "apps/sim/triggers/registry.ts": 522, "apps/sim/blocks/registry.ts": 367, "apps/sim/blocks/registry-maps.ts": 365, - "apps/sim/app/workspace/[workspaceId]/skills/components/skill-fields/index.ts": 161, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-field.tsx": 158, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/index.ts": 80, - "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/use-markdown-mentions.ts": 78 + "apps/sim/app/workspace/[workspaceId]/skills/components/skill-fields/index.ts": 171, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-field.tsx": 168, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/index.ts": 91, + "apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/use-markdown-mentions.ts": 89 } }, "app/workspace/[workspaceId]/skills/page.tsx": { - "modules": 1106, + "modules": 1102, "gateways": { - "apps/sim/app/workspace/[workspaceId]/skills/skills.tsx": 949, - "apps/sim/app/workspace/[workspaceId]/integrations/components/showcase-with-explore/index.ts": 937, + "apps/sim/app/workspace/[workspaceId]/skills/skills.tsx": 950, + "apps/sim/app/workspace/[workspaceId]/integrations/components/showcase-with-explore/index.ts": 938, "apps/sim/blocks/registry.ts": 928, "apps/sim/blocks/registry-maps.ts": 926, "apps/sim/triggers/index.ts": 524, "apps/sim/triggers/registry.ts": 522, - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 67, - "apps/sim/lib/api/contracts/index.ts": 43 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 60, + "apps/sim/lib/api/contracts/index.ts": 44 } }, "app/workspace/[workspaceId]/tables/[tableId]/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/tables/[tableId]/loading.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/tables/[tableId]/page.tsx": { - "modules": 1874, + "modules": 1871, "gateways": { - "apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx": 1696, + "apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx": 1692, "apps/sim/triggers/registry.ts": 522, - "apps/sim/app/workspace/[workspaceId]/w/components/preview/index.ts": 366, + "apps/sim/app/workspace/[workspaceId]/w/components/preview/index.ts": 365, "apps/sim/blocks/registry.ts": 331, - "apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/index.ts": 320, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 316, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 265, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts": 259 + "apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-editor/index.ts": 319, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 315, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 264, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts": 258 } }, "app/workspace/[workspaceId]/tables/error.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/tables/loading.tsx": { - "modules": 157, + "modules": 152, "gateways": { - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/tables/page.tsx": { - "modules": 1971, + "modules": 1978, "gateways": { "apps/sim/triggers/registry.ts": 485, - "apps/sim/lib/auth/index.ts": 416, + "apps/sim/lib/auth/index.ts": 420, "apps/sim/blocks/registry.ts": 354, - "apps/sim/app/workspace/[workspaceId]/tables/tables.tsx": 192, + "apps/sim/app/workspace/[workspaceId]/tables/tables.tsx": 195, "apps/sim/lib/webhooks/providers/index.ts": 117, "apps/sim/lib/webhooks/providers/registry.ts": 115, "apps/sim/app/workspace/[workspaceId]/tables/prefetch.ts": 98, @@ -614,9 +593,9 @@ } }, "app/workspace/[workspaceId]/upgrade/page.tsx": { - "modules": 141, + "modules": 143, "gateways": { - "apps/sim/app/workspace/[workspaceId]/upgrade/upgrade.tsx": 134, + "apps/sim/app/workspace/[workspaceId]/upgrade/upgrade.tsx": 136, "apps/sim/app/workspace/[workspaceId]/upgrade/hooks/index.ts": 84, "apps/sim/lib/billing/client/upgrade.ts": 76, "apps/sim/hooks/queries/organization.ts": 71, @@ -625,44 +604,44 @@ } }, "app/workspace/[workspaceId]/w/[workflowId]/layout.tsx": { - "modules": 159, + "modules": 154, "gateways": { - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx": 158, - "apps/sim/app/workspace/[workspaceId]/components/index.ts": 156, - "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 98, - "apps/sim/hooks/queries/copilot-feedback.ts": 60 + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/error/index.tsx": 153, + "apps/sim/app/workspace/[workspaceId]/components/index.ts": 151, + "apps/sim/app/workspace/[workspaceId]/components/message-actions/index.ts": 116, + "apps/sim/hooks/queries/copilot-feedback.ts": 65 } }, "app/workspace/[workspaceId]/w/[workflowId]/page.tsx": { - "modules": 2155, + "modules": 2168, "gateways": { - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 2154, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 2167, "apps/sim/triggers/registry.ts": 522, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 371, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 379, "apps/sim/blocks/registry.ts": 350, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 334, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 268, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 342, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 275, "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 168, "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 149 } }, "app/workspace/[workspaceId]/w/page.tsx": { - "modules": 2136, + "modules": 2149, "gateways": { - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 950, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 624, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 963, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 633, "apps/sim/triggers/registry.ts": 522, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 355, "apps/sim/blocks/registry.ts": 350, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 347, "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 175, "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 155, "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/index.ts": 150 } }, "app/workspace/layout.tsx": { - "modules": 1107, + "modules": 1112, "gateways": { - "apps/sim/app/workspace/providers/socket-provider.tsx": 1097, + "apps/sim/app/workspace/providers/socket-provider.tsx": 1102, "apps/sim/triggers/registry.ts": 522, "apps/sim/blocks/registry.ts": 375, "apps/sim/blocks/registry-maps.ts": 372, @@ -673,16 +652,16 @@ } }, "app/workspace/page.tsx": { - "modules": 1109, + "modules": 1112, "gateways": { - "apps/sim/lib/auth/stale-session-recovery.ts": 1014, + "apps/sim/lib/auth/stale-session-recovery.ts": 1016, "apps/sim/triggers/index.ts": 524, "apps/sim/triggers/registry.ts": 522, "apps/sim/blocks/registry.ts": 375, "apps/sim/blocks/registry-maps.ts": 372, - "apps/sim/stores/workflows/registry/store.ts": 41, - "apps/sim/lib/api/contracts/index.ts": 38, - "apps/sim/hooks/queries/deployments.ts": 36 + "apps/sim/stores/workflows/registry/store.ts": 43, + "apps/sim/hooks/queries/deployments.ts": 38, + "apps/sim/lib/api/contracts/index.ts": 38 } }, "lib/catalog/projection/block-detail.ts": { @@ -690,8 +669,8 @@ "gateways": { "apps/sim/triggers/index.ts": 524, "apps/sim/triggers/registry.ts": 522, - "apps/sim/lib/catalog/projection/block-summary.ts": 406, - "apps/sim/blocks/registry-maps.ts": 402, + "apps/sim/lib/catalog/projection/block-summary.ts": 405, + "apps/sim/blocks/registry-maps.ts": 401, "apps/sim/triggers/clickup/index.ts": 32 } }, @@ -718,16 +697,16 @@ "gateways": {} }, "lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts": { - "modules": 1076, + "modules": 1077, "gateways": { "apps/sim/triggers/index.ts": 524, "apps/sim/triggers/registry.ts": 522, - "apps/sim/blocks/registry.ts": 399, - "apps/sim/blocks/registry-maps.ts": 397, - "apps/sim/lib/permission-groups/config-scope.server.ts": 94, - "apps/sim/lib/permission-groups/resolve.server.ts": 92, - "apps/sim/lib/billing/core/subscription.ts": 86, - "apps/sim/components/emails/index.ts": 54 + "apps/sim/blocks/registry.ts": 398, + "apps/sim/blocks/registry-maps.ts": 396, + "apps/sim/lib/permission-groups/config-scope.server.ts": 95, + "apps/sim/lib/permission-groups/resolve.server.ts": 93, + "apps/sim/lib/billing/core/subscription.ts": 87, + "apps/sim/components/emails/index.ts": 55 } } } From 95b5b76a7abfbaa781778b664e17457765df465a Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 16 Sep 2026 13:28:23 -0700 Subject: [PATCH 18/43] fix(search): bound retrieval and avoid repeated turn admission (#7888) * fix(search): bound retrieval and billing query cost * refactor(billing): remove unneeded synchronous cost projection * fix(billing): authorize continuations without repeating spend admission * fix(test): align session policy fixture with joined billing read --- .github/workflows/test-build.yml | 12 +- .../copilot/api-keys/validate/route.test.ts | 340 +- .../api/copilot/api-keys/validate/route.ts | 134 +- .../app/api/copilot/chat/abort/route.test.ts | 20 +- apps/sim/app/api/copilot/chat/abort/route.ts | 6 +- apps/sim/lib/api/contracts/copilot.ts | 10 + apps/sim/lib/auth/session-hooks.test.ts | 4 +- .../calculations/usage-reservation.test.ts | 100 +- apps/sim/lib/billing/core/access.test.ts | 77 +- apps/sim/lib/billing/core/access.ts | 41 +- .../billing/core/usage-log.postgres.test.ts | 50 + apps/sim/lib/billing/core/usage.test.ts | 96 + apps/sim/lib/billing/core/usage.ts | 50 +- .../authorize-chat-callback.test.ts | 224 + .../application/authorize-chat-callback.ts | 107 + .../sim/lib/copilot/application/operations.ts | 19 + apps/sim/lib/copilot/chat/lifecycle.test.ts | 106 +- apps/sim/lib/copilot/chat/lifecycle.ts | 51 +- .../copilot/chat/organization-chats.test.ts | 41 + .../lib/copilot/chat/organization-chats.ts | 23 + .../copilot/generated/billing-protocol-v1.ts | 14 + apps/sim/lib/copilot/request/lifecycle/run.ts | 10 +- .../search-latency.integration.ts | 182 +- apps/sim/lib/knowledge/search/budget.ts | 5 +- apps/sim/lib/knowledge/search/queries.test.ts | 84 +- apps/sim/lib/knowledge/search/queries.ts | 35 +- .../0352_search_document_lookup.sql | 2 + .../db/migrations/meta/0352_snapshot.json | 27079 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 4 + ...rations-paused-billing-attribution.test.ts | 1 + ...6_backfill_search_vectors.postgres.test.ts | 44 +- .../0016_backfill_search_vectors.ts | 5 + .../0017_index_search_documents.ts | 7 + packages/db/script-migrations/index.ts | 2 + scripts/sync-billing-protocol-contract.ts | 20 + scripts/test-knowledge-acls.ts | 1 + 37 files changed, 28863 insertions(+), 150 deletions(-) create mode 100644 apps/sim/lib/copilot/application/authorize-chat-callback.test.ts create mode 100644 apps/sim/lib/copilot/application/authorize-chat-callback.ts create mode 100644 packages/db/migrations/0352_search_document_lookup.sql create mode 100644 packages/db/migrations/meta/0352_snapshot.json create mode 100644 packages/db/script-migrations/0017_index_search_documents.ts diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 5fd8dee1725..e9f77ac602c 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -17,6 +17,15 @@ jobs: matrix: provision: [push, migrate] services: + redis: + image: redis:8.2-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 5s + --health-timeout 5s + --health-retries 10 postgres: image: pgvector/pgvector:pg17 env: @@ -109,7 +118,8 @@ jobs: working-directory: apps/sim env: BILLING_USAGE_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim - run: bunx vitest run lib/billing/core/usage-log.postgres.test.ts lib/billing/core/organization-activity.postgres.test.ts + BILLING_USAGE_TEST_REDIS_URL: redis://127.0.0.1:6379 + run: bunx vitest run lib/billing/core/usage-log.postgres.test.ts lib/billing/core/organization-activity.postgres.test.ts lib/billing/calculations/usage-reservation.test.ts - name: Verify cumulative billing timeout recovery on PostgreSQL 16 if: matrix.provision == 'push' diff --git a/apps/sim/app/api/copilot/api-keys/validate/route.test.ts b/apps/sim/app/api/copilot/api-keys/validate/route.test.ts index 42208e91a8d..64ced142086 100644 --- a/apps/sim/app/api/copilot/api-keys/validate/route.test.ts +++ b/apps/sim/app/api/copilot/api-keys/validate/route.test.ts @@ -28,6 +28,8 @@ const { mockGetUserEntityPermissions, mockGetWorkspaceBillingSettings, mockAuthorizeOrganizationChat, + mockAuthorizeCallback, + mockCheckContinuationBilling, } = vi.hoisted(() => ({ mockCheckInternalApiKey: vi.fn(), mockCheckAttributedUsageLimits: vi.fn(), @@ -44,6 +46,8 @@ const { mockGetUserEntityPermissions: vi.fn(), mockGetWorkspaceBillingSettings: vi.fn(), mockAuthorizeOrganizationChat: vi.fn(), + mockAuthorizeCallback: vi.fn(), + mockCheckContinuationBilling: vi.fn(), })) const ATTRIBUTION = { @@ -85,7 +89,8 @@ const SELF_HOSTED_OPAQUE_WORKSPACE_VALIDATE_BODY = { workspaceId: 'local-self-hosted-workspace', } as const -vi.mock('@/lib/billing/core/billing-attribution', () => ({ +vi.mock('@/lib/billing/core/billing-attribution', async (importOriginal) => ({ + ...(await importOriginal()), BILLING_ACCOUNT_DECISION_HEADER: 'x-sim-billing-account-decision', BILLING_ATTRIBUTION_HEADER: 'x-sim-billing-attribution', BILLING_REQUEST_ID_HEADER: 'x-sim-billing-request-id', @@ -120,6 +125,11 @@ vi.mock('@/lib/billing/core/usage-log', () => ({ deriveBillingContext: mockDeriveBillingContext, })) +vi.mock('@/lib/copilot/application/authorize-chat-callback', () => ({ + authorizeCopilotChatCallback: mockAuthorizeCallback, + checkCopilotContinuationBilling: mockCheckContinuationBilling, +})) + vi.mock('@/lib/copilot/chat/organization-chats', () => ({ authorizeOrganizationChatDelegation: { execute: mockAuthorizeOrganizationChat }, })) @@ -158,7 +168,9 @@ describe('POST /api/copilot/api-keys/validate billing protocols', () => { beforeEach(() => { vi.clearAllMocks() resetDbChainMock() - setEnvFlags({ isHosted: false }) + setEnvFlags({ isHosted: false, isBillingEnabled: false }) + mockAuthorizeCallback.mockResolvedValue(undefined) + mockCheckContinuationBilling.mockResolvedValue({ blocked: false }) mockCheckInternalApiKey.mockReturnValue({ success: true }) queueTableRows(schemaMock.user, [{ id: 'user-1' }]) mockResolveBillingAttribution.mockResolvedValue(ATTRIBUTION) @@ -558,3 +570,327 @@ describe('POST /api/copilot/api-keys/validate billing protocols', () => { expect(mockResolveBillingAttribution).not.toHaveBeenCalled() }) }) + +describe('validation lifecycle purposes', () => { + const requestId = '0190c03f-9f7d-4b79-8b58-e7f779fd29e1' + const encode = (value: object) => encodeURIComponent(JSON.stringify(value)) + const attributedHeaders = { + 'x-sim-billing-protocol': 'attribution-v1', + 'x-sim-billing-request-id': requestId, + 'x-sim-billing-attribution': encode(ATTRIBUTION), + } + const directHeaders = { + 'x-sim-billing-protocol': 'direct-v1', + 'x-sim-billing-request-id': requestId, + 'x-sim-billing-account-decision': encode(ACCOUNT_BILLING_DECISION), + } + const body = { userId: 'user-1', workspaceId: 'ws-1', chatId: 'chat-1', purpose: 'continuation' } + + beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + setEnvFlags({ isHosted: true, isBillingEnabled: true }) + for (let call = 0; call < 3; call++) queueTableRows(schemaMock.user, [{ id: 'user-1' }]) + mockCheckInternalApiKey.mockReturnValue({ success: true }) + mockAuthorizeCallback.mockReset().mockResolvedValue(undefined) + mockCheckContinuationBilling.mockReset().mockResolvedValue({ blocked: false }) + mockIsEnterprisePlan.mockResolvedValue(false) + }) + + it('defaults older callers to full admission and rejects unknown purposes', () => { + expect(validateCopilotApiKeyBodySchema.parse({ userId: 'user-1' }).purpose).toBe('new-turn') + expect( + validateCopilotApiKeyBodySchema.safeParse({ userId: 'user-1', purpose: 'skip' }).success + ).toBe(false) + }) + + it.each(['continuation', 'cancellation'])( + 'authenticates before processing %s', + async (purpose) => { + mockCheckInternalApiKey.mockReturnValueOnce({ success: false }) + expect((await POST(request({ ...body, purpose }, attributedHeaders))).status).toBe(401) + expect(mockAuthorizeCallback).not.toHaveBeenCalled() + expect(mockCheckContinuationBilling).not.toHaveBeenCalled() + } + ) + + it('checks original payer and current scope without repeating spend admission', async () => { + const response = await POST(request(body, attributedHeaders)) + expect(response.status).toBe(200) + expect(mockAuthorizeCallback).toHaveBeenCalledWith({ ...body, delegationId: requestId }) + expect(mockCheckContinuationBilling).toHaveBeenCalledWith({ + kind: 'attributed', + attribution: ATTRIBUTION, + }) + expect(mockAuthorizeCallback.mock.invocationCallOrder[0]).toBeLessThan( + mockCheckContinuationBilling.mock.invocationCallOrder[0] + ) + expect(mockCheckAttributedUsageLimits).not.toHaveBeenCalled() + expect(mockCheckServerSideUsageLimits).not.toHaveBeenCalled() + expect(mockResolveLegacyV0BillingAttribution).not.toHaveBeenCalled() + expect(mockGetHighestPrioritySubscription).not.toHaveBeenCalled() + expect(response.headers.get('x-sim-billing-attribution')).toBeNull() + expect(response.headers.get('x-sim-billing-account-decision')).toBeNull() + }) + + it('refreshes entitlement with the stored account payer and opaque direct scope', async () => { + mockIsEnterprisePlan.mockResolvedValueOnce(true) + const response = await POST( + request({ ...body, workspaceId: 'opaque-local-workspace' }, directHeaders) + ) + expect(response.status).toBe(200) + await expect(response.json()).resolves.toEqual({ isEnterprise: true }) + expect(mockCheckContinuationBilling).toHaveBeenCalledWith({ + kind: 'account', + decision: ACCOUNT_BILLING_DECISION, + }) + expect(mockAuthorizeCallback).not.toHaveBeenCalled() + expect(mockGetHighestPrioritySubscription).not.toHaveBeenCalled() + expect(mockDeriveBillingContext).not.toHaveBeenCalled() + expect(mockCheckServerSideUsageLimits).not.toHaveBeenCalled() + expect(response.headers.get('x-sim-billing-account-decision')).toBeNull() + }) + + it.each([ + ['missing attribution', { ...attributedHeaders, 'x-sim-billing-attribution': '' }], + [ + 'malformed attribution', + { ...attributedHeaders, 'x-sim-billing-attribution': 'invalid-json' }, + ], + ['missing id', { ...attributedHeaders, 'x-sim-billing-request-id': '' }], + [ + 'conflicting material', + { ...attributedHeaders, 'x-sim-billing-account-decision': encode(ACCOUNT_BILLING_DECISION) }, + ], + [ + 'another actor', + { + ...attributedHeaders, + 'x-sim-billing-attribution': encode({ ...ATTRIBUTION, actorUserId: 'other-user' }), + }, + ], + [ + 'another workspace', + { + ...attributedHeaders, + 'x-sim-billing-attribution': encode({ ...ATTRIBUTION, workspaceId: 'other-workspace' }), + }, + ], + ['missing account decision', { ...directHeaders, 'x-sim-billing-account-decision': '' }], + [ + 'malformed account decision', + { ...directHeaders, 'x-sim-billing-account-decision': 'invalid-json' }, + ], + [ + 'different account actor', + { + ...directHeaders, + 'x-sim-billing-account-decision': encode({ + ...ACCOUNT_BILLING_DECISION, + userId: 'other-user', + }), + }, + ], + [ + 'direct conflicting material', + { ...directHeaders, 'x-sim-billing-attribution': encode(ATTRIBUTION) }, + ], + ])('rejects %s before authorization or billing', async (_label, headers) => { + expect((await POST(request(body, headers))).status).toBe(400) + expect(mockAuthorizeCallback).not.toHaveBeenCalled() + expect(mockCheckContinuationBilling).not.toHaveBeenCalled() + expect(mockCheckAttributedUsageLimits).not.toHaveBeenCalled() + expect(mockCheckServerSideUsageLimits).not.toHaveBeenCalled() + }) + + it('binds organization continuation to original actor, scope and private chat', async () => { + const attribution = { ...ATTRIBUTION, workspaceId: null } + const orgBody = { + userId: 'user-1', + organizationId: 'org-1', + chatId: 'chat-1', + purpose: 'continuation', + } + const headers = { ...attributedHeaders, 'x-sim-billing-attribution': encode(attribution) } + expect((await POST(request(orgBody, headers))).status).toBe(200) + expect(mockAuthorizeCallback).toHaveBeenCalledWith({ ...orgBody, delegationId: requestId }) + expect(mockCheckContinuationBilling).toHaveBeenCalledWith({ kind: 'attributed', attribution }) + expect((await POST(request({ ...orgBody, organizationId: 'other-org' }, headers))).status).toBe( + 400 + ) + }) + + it.each(['continuation', 'cancellation'])('rejects a deleted actor on %s', async (purpose) => { + resetDbChainMock() + queueTableRows(schemaMock.user, []) + expect((await POST(request({ ...body, purpose }, attributedHeaders))).status).toBe(403) + expect(mockAuthorizeCallback).not.toHaveBeenCalled() + expect(mockCheckContinuationBilling).not.toHaveBeenCalled() + }) + + it.each(['continuation', 'cancellation'])( + 'rejects revoked scope on %s before billing', + async (purpose) => { + mockAuthorizeCallback.mockRejectedValueOnce( + new OrchestrationError('forbidden', 'Access revoked') + ) + expect((await POST(request({ ...body, purpose }, attributedHeaders))).status).toBe(403) + expect(mockCheckContinuationBilling).not.toHaveBeenCalled() + } + ) + + it('fails closed on scope or account-standing infrastructure errors', async () => { + mockAuthorizeCallback.mockRejectedValueOnce(new Error('database unavailable')) + expect((await POST(request(body, attributedHeaders))).status).toBe(500) + mockCheckContinuationBilling.mockRejectedValueOnce(new Error('database unavailable')) + expect((await POST(request(body, attributedHeaders))).status).toBe(500) + }) + + it.each(['actor', 'payer'])('refuses a newly blocked %s on continuation', async (scope) => { + mockCheckContinuationBilling.mockResolvedValueOnce({ blocked: true, scope }) + expect((await POST(request(body, attributedHeaders))).status).toBe(402) + expect(mockCheckAttributedUsageLimits).not.toHaveBeenCalled() + }) + + it('allows cancellation without billing material or spending/standing/plan checks', async () => { + const response = await POST( + request({ ...body, purpose: 'cancellation' }, { 'x-sim-billing-protocol': 'attribution-v1' }) + ) + expect(response.status).toBe(200) + expect(mockAuthorizeCallback).toHaveBeenCalledWith( + expect.objectContaining({ purpose: 'cancellation', workspaceId: 'ws-1' }) + ) + expect(mockCheckContinuationBilling).not.toHaveBeenCalled() + expect(mockCheckAttributedUsageLimits).not.toHaveBeenCalled() + expect(mockCheckServerSideUsageLimits).not.toHaveBeenCalled() + expect(mockIsEnterprisePlan).not.toHaveBeenCalled() + await expect(response.json()).resolves.toEqual({ isEnterprise: false }) + }) + + it('reuses a legacy checkpoint snapshot without selecting a new payer', async () => { + const response = await POST( + request(body, { + 'x-sim-billing-protocol': 'legacy-v0', + 'x-sim-billing-attribution': encode(ATTRIBUTION), + }) + ) + expect(response.status).toBe(200) + expect(mockCheckContinuationBilling).toHaveBeenCalledWith({ + kind: 'attributed', + attribution: ATTRIBUTION, + }) + expect(mockResolveLegacyV0BillingAttribution).not.toHaveBeenCalled() + }) + + it.each([true, false])( + 'rejects a legacy organization snapshot with omitted scope even when hosted=%s', + async (isHosted) => { + setEnvFlags({ isHosted, isBillingEnabled: isHosted }) + const response = await POST( + request( + { userId: 'user-1', purpose: 'continuation' }, + { + 'x-sim-billing-protocol': 'legacy-v0', + 'x-sim-billing-attribution': encode({ ...ATTRIBUTION, workspaceId: null }), + } + ) + ) + expect(response.status).toBe(400) + expect(mockAuthorizeCallback).not.toHaveBeenCalled() + expect(mockCheckContinuationBilling).not.toHaveBeenCalled() + } + ) + + it.each([ + [true, true, 400], + [false, true, 400], + [false, false, 200], + ])( + 'allows missing legacy material only for unbilled self-hosting (%s, %s)', + async (isHosted, isBillingEnabled, status) => { + setEnvFlags({ isHosted, isBillingEnabled }) + expect((await POST(request(body, { 'x-sim-billing-protocol': 'legacy-v0' }))).status).toBe( + status + ) + expect(mockCheckContinuationBilling).not.toHaveBeenCalled() + expect(mockResolveLegacyV0BillingAttribution).not.toHaveBeenCalled() + } + ) + + it.each(['continuation', 'cancellation'])( + 'preserves markerless unbilled local %s with an opaque workspace', + async (purpose) => { + setEnvFlags({ isHosted: false, isBillingEnabled: false }) + expect( + (await POST(request({ ...body, workspaceId: 'opaque-local-workspace', purpose }))).status + ).toBe(200) + expect(mockAuthorizeCallback).not.toHaveBeenCalled() + expect(mockCheckContinuationBilling).not.toHaveBeenCalled() + expect(mockResolveLegacyV0BillingAttribution).not.toHaveBeenCalled() + } + ) + + it.each(['continuation', 'cancellation'])( + 'still checks snapshot scope on unbilled local %s', + async (purpose) => { + setEnvFlags({ isHosted: false, isBillingEnabled: false }) + const headers = { + 'x-sim-billing-protocol': 'legacy-v0', + 'x-sim-billing-attribution': encode(ATTRIBUTION), + } + expect((await POST(request({ ...body, purpose }, headers))).status).toBe(200) + expect(mockAuthorizeCallback).toHaveBeenCalledWith( + expect.objectContaining({ purpose, workspaceId: 'ws-1' }) + ) + } + ) + + it.each([ + ['attribution-v1', false, false], + ['legacy-v0', true, true], + ['legacy-v0', true, false], + ['legacy-v0', false, true], + ])( + 'checks cancellation scope for %s (hosted=%s, billing=%s)', + async (protocol, isHosted, isBillingEnabled) => { + setEnvFlags({ isHosted, isBillingEnabled }) + expect( + ( + await POST( + request({ ...body, purpose: 'cancellation' }, { 'x-sim-billing-protocol': protocol }) + ) + ).status + ).toBe(200) + expect(mockAuthorizeCallback).toHaveBeenCalled() + } + ) + + it('refuses hosted cancellation without a protocol or resource scope', async () => { + expect((await POST(request({ ...body, purpose: 'cancellation' }))).status).toBe(400) + expect( + ( + await POST( + request( + { userId: 'user-1', purpose: 'cancellation' }, + { 'x-sim-billing-protocol': 'attribution-v1' } + ) + ) + ).status + ).toBe(400) + expect(mockAuthorizeCallback).not.toHaveBeenCalled() + }) + + it('checks fresh spending on the next new turn and refuses supplied account decisions', async () => { + expect((await POST(request(body, attributedHeaders))).status).toBe(200) + mockCheckAttributedUsageLimits.mockResolvedValueOnce({ + isExceeded: true, + payerUsage: { currentUsage: 120, limit: 100 }, + }) + expect((await POST(request({ ...body, purpose: 'new-turn' }, attributedHeaders))).status).toBe( + 402 + ) + expect(mockCheckAttributedUsageLimits).toHaveBeenCalledTimes(1) + expect((await POST(request({ ...body, purpose: 'new-turn' }, directHeaders))).status).toBe(400) + expect(mockCheckServerSideUsageLimits).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/app/api/copilot/api-keys/validate/route.ts b/apps/sim/app/api/copilot/api-keys/validate/route.ts index 970220f9099..174a22250af 100644 --- a/apps/sim/app/api/copilot/api-keys/validate/route.ts +++ b/apps/sim/app/api/copilot/api-keys/validate/route.ts @@ -11,7 +11,9 @@ import { type AccountBillingDecision, type BillingAttributionSnapshot, checkAttributedUsageLimits, + requireAccountBillingDecisionHeader, requireBillingAttributionHeader, + requireBillingCallbackAttribution, requireBillingRequestIdHeader, resolveLegacyV0BillingAttribution, resolveOrganizationBillingAttribution, @@ -21,6 +23,11 @@ import { import { getHighestPrioritySubscription } from '@/lib/billing/core/plan' import { isEnterprisePlan } from '@/lib/billing/core/subscription' import { deriveBillingContext } from '@/lib/billing/core/usage-log' +import { + authorizeCopilotChatCallback, + type CopilotContinuationBilling, + checkCopilotContinuationBilling, +} from '@/lib/copilot/application/authorize-chat-callback' import { COPILOT_APPLICATION_DELEGATION_TTL_MS, createTrustedOrganizationCopilotPrincipal, @@ -32,6 +39,7 @@ import { BILLING_REQUEST_ID_HEADER, COPILOT_BILLING_PROTOCOL, COPILOT_BILLING_PROTOCOL_HEADER, + COPILOT_VALIDATION_PURPOSE, type CopilotBillingProtocol, } from '@/lib/copilot/generated/billing-protocol-v1' import { CopilotValidateOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1' @@ -39,7 +47,7 @@ import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1' import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1' import { checkInternalApiKey } from '@/lib/copilot/request/http' import { withIncomingGoSpan } from '@/lib/copilot/request/otel' -import { isHosted } from '@/lib/core/config/env-flags' +import { isBillingEnabled, isHosted } from '@/lib/core/config/env-flags' import { asOrchestrationError } from '@/lib/core/orchestration/types' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -69,12 +77,12 @@ type AdmissionBillingDecision = } /** - * Resolves admission against the versioned Go callback protocol. + * Resolves new-turn admission against the versioned Go callback protocol. * * Markerless self-hosted admission is legacy-v0. A locally resolvable * workspace selects its current payer; an absent or opaque workspace preserves - * account billing. This mutable resolution is repeated at callback time for - * local self-hosted compatibility. Direct-v1 remains scoped only to the + * account billing. Only new-turn admission resolves a mutable payer; continuation + * restores the original checkpoint decision. Direct-v1 remains scoped only to the * authenticated Chat/Copilot key owner's hosted account, and attributed-v1 * never falls back from its immutable envelope. */ @@ -178,6 +186,52 @@ async function resolveAdmissionBillingDecision( return { kind: 'legacy-account', userId: actorUserId } } +/** Restores only the checkpoint's admitted payer; continuation never re-resolves billing. */ +function resolveContinuationBilling( + req: NextRequest, + protocol: CopilotBillingProtocol | undefined, + scope: { userId: string; workspaceId?: string; organizationId?: string } +): CopilotContinuationBilling | null | NextResponse { + const hasAttribution = Boolean(req.headers.get(BILLING_ATTRIBUTION_HEADER)) + const hasDecision = Boolean(req.headers.get(BILLING_ACCOUNT_DECISION_HEADER)) + try { + if (protocol === COPILOT_BILLING_PROTOCOL.direct) { + if (hasAttribution) return invalidBillingProtocolResponse() + requireBillingRequestIdHeader(req.headers) + const decision = requireAccountBillingDecisionHeader(req.headers) + if (decision.userId !== scope.userId) return invalidBillingProtocolResponse() + return { kind: 'account', decision } + } + if (hasDecision) return invalidBillingProtocolResponse() + if (protocol === COPILOT_BILLING_PROTOCOL.attributed) { + if (!scope.workspaceId && !scope.organizationId) return invalidBillingProtocolResponse() + requireBillingRequestIdHeader(req.headers) + } else { + if (protocol !== undefined && protocol !== COPILOT_BILLING_PROTOCOL.legacy) { + return invalidBillingProtocolResponse() + } + if (req.headers.has(BILLING_REQUEST_ID_HEADER)) return invalidBillingProtocolResponse() + if (protocol === undefined && (isHosted || hasAttribution)) { + return invalidBillingProtocolResponse() + } + if (!hasAttribution) { + return !isHosted && !isBillingEnabled ? null : invalidBillingProtocolResponse() + } + } + if (!scope.workspaceId && !scope.organizationId) return invalidBillingProtocolResponse() + return { + kind: 'attributed', + attribution: requireBillingCallbackAttribution(req.headers, { + actorUserId: scope.userId, + workspaceId: scope.workspaceId, + organizationId: scope.organizationId, + }), + } + } catch { + return invalidBillingProtocolResponse() + } +} + async function checkAdmissionUsage(admission: AdmissionBillingDecision): Promise<{ isExceeded: boolean currentUsage: number @@ -287,7 +341,8 @@ export const POST = withRouteHandler((req: NextRequest) => ) if (!parsed.success) return parsed.response - const { userId, workspaceId, organizationId, chatId } = parsed.data.body + const { userId, workspaceId, organizationId, chatId, purpose } = parsed.data.body + const startedAt = performance.now() const protocol = parsed.data.headers?.[COPILOT_BILLING_PROTOCOL_HEADER] span.setAttribute(TraceAttr.UserId, userId) @@ -299,7 +354,72 @@ export const POST = withRouteHandler((req: NextRequest) => return NextResponse.json({ error: 'User not found' }, { status: 403 }) } - logger.info('[API VALIDATION] Validating usage limit', { userId }) + if (purpose !== COPILOT_VALIDATION_PURPOSE.newTurn) { + const billing = + purpose === COPILOT_VALIDATION_PURPOSE.continuation + ? resolveContinuationBilling(req, protocol, { userId, workspaceId, organizationId }) + : null + if (billing instanceof NextResponse || (protocol === undefined && isHosted)) { + span.setAttribute(TraceAttr.CopilotValidateOutcome, CopilotValidateOutcome.InvalidBody) + span.setAttribute(TraceAttr.HttpStatusCode, 400) + return billing instanceof NextResponse ? billing : invalidBillingProtocolResponse() + } + + /** Unbilled local legacy turns have no admitted Sim resource scope to restore. */ + const localUnbilledCallback = + (protocol === undefined || protocol === COPILOT_BILLING_PROTOCOL.legacy) && + !req.headers.has(BILLING_ATTRIBUTION_HEADER) && + !req.headers.has(BILLING_ACCOUNT_DECISION_HEADER) && + !isHosted && + !isBillingEnabled + if ( + purpose === COPILOT_VALIDATION_PURPOSE.cancellation && + protocol !== COPILOT_BILLING_PROTOCOL.direct && + !localUnbilledCallback && + !workspaceId && + !organizationId + ) { + span.setAttribute(TraceAttr.CopilotValidateOutcome, CopilotValidateOutcome.InvalidBody) + span.setAttribute(TraceAttr.HttpStatusCode, 400) + return invalidBillingProtocolResponse() + } + /** Direct keys carry self-hosted scope IDs that do not name hosted Sim resources. */ + if (protocol !== COPILOT_BILLING_PROTOCOL.direct && !localUnbilledCallback) { + await authorizeCopilotChatCallback({ + userId, + workspaceId, + organizationId, + chatId, + purpose, + delegationId: req.headers.get(BILLING_REQUEST_ID_HEADER) ?? generateId(), + }) + } + const blocked = billing ? await checkCopilotContinuationBilling(billing) : null + logger.info('[API VALIDATION] Lifecycle authorization validated', { + userId, + purpose, + billingProtocol: protocol ?? COPILOT_BILLING_PROTOCOL.legacy, + blocked: blocked?.blocked ?? false, + elapsedMs: Math.round(performance.now() - startedAt), + }) + if (blocked?.blocked) { + span.setAttribute( + TraceAttr.CopilotValidateOutcome, + CopilotValidateOutcome.UsageExceeded + ) + span.setAttribute(TraceAttr.HttpStatusCode, 402) + return new NextResponse(null, { status: 402 }) + } + const isEnterprise = + purpose === COPILOT_VALIDATION_PURPOSE.cancellation + ? false + : await isEnterprisePlan(userId) + span.setAttribute(TraceAttr.CopilotValidateOutcome, CopilotValidateOutcome.Ok) + span.setAttribute(TraceAttr.HttpStatusCode, 200) + return NextResponse.json({ isEnterprise }) + } + + logger.info('[API VALIDATION] Validating usage limit', { userId, purpose }) const admission = await resolveAdmissionBillingDecision( req, protocol, @@ -323,6 +443,8 @@ export const POST = withRouteHandler((req: NextRequest) => logger.info('[API VALIDATION] Usage limit validated', { userId, + purpose, + elapsedMs: Math.round(performance.now() - startedAt), currentUsage, limit, isExceeded: usage.isExceeded, diff --git a/apps/sim/app/api/copilot/chat/abort/route.test.ts b/apps/sim/app/api/copilot/chat/abort/route.test.ts index dcd1c74dd33..ace30ab46d8 100644 --- a/apps/sim/app/api/copilot/chat/abort/route.test.ts +++ b/apps/sim/app/api/copilot/chat/abort/route.test.ts @@ -33,7 +33,7 @@ const { }) vi.mock('@/lib/copilot/chat/lifecycle', () => ({ - getAccessibleCopilotChatAuth: mockGetAccessibleChat, + getAccessibleCopilotChatForCancellation: mockGetAccessibleChat, })) vi.mock('@/lib/copilot/request/http', () => ({ @@ -100,6 +100,24 @@ describe('POST /api/copilot/chat/abort', () => { expect(mockReleasePendingChatStream).toHaveBeenCalledWith('chat-1', 'stream-1') }) + it('authorizes org Stop using the cancellation lookup and forwards the canonical scope', async () => { + const principal = { kind: 'session', userId: 'user-1', sessionId: 'session-1' } + mockAuthenticate.mockResolvedValueOnce({ userId: 'user-1', isAuthenticated: true, principal }) + mockGetLatestRunForStream.mockResolvedValueOnce({ chatId: 'chat-1', workspaceId: null }) + mockGetAccessibleChat.mockResolvedValueOnce({ id: 'chat-1', organizationId: 'org-1' }) + const response = await POST(abortRequest()) + expect(response.status).toBe(200) + expect(mockGetAccessibleChat).toHaveBeenCalledWith('chat-1', 'user-1', { principal }) + expect(mockRequestExplicitStreamAbort).toHaveBeenCalledWith( + expect.objectContaining({ + chatId: 'chat-1', + userId: 'user-1', + organizationId: 'org-1', + workspaceId: undefined, + }) + ) + }) + it('refuses an inaccessible organization chat before changing stream state', async () => { mockGetAccessibleChat.mockResolvedValueOnce(null) const response = await POST(abortRequest()) diff --git a/apps/sim/app/api/copilot/chat/abort/route.ts b/apps/sim/app/api/copilot/chat/abort/route.ts index 8aea11d6439..420e5b6eeda 100644 --- a/apps/sim/app/api/copilot/chat/abort/route.ts +++ b/apps/sim/app/api/copilot/chat/abort/route.ts @@ -4,7 +4,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { copilotChatAbortBodySchema } from '@/lib/api/contracts/copilot' import { validationErrorResponse } from '@/lib/api/server' import { getLatestRunForStream } from '@/lib/copilot/async-runs/repository' -import { getAccessibleCopilotChatAuth } from '@/lib/copilot/chat/lifecycle' +import { getAccessibleCopilotChatForCancellation } from '@/lib/copilot/chat/lifecycle' import { CopilotAbortOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1' import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1' import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1' @@ -75,7 +75,9 @@ export const POST = withRouteHandler((request: NextRequest) => return NextResponse.json({ error: 'Stream not found' }, { status: 404 }) } const chat = run.chatId - ? await getAccessibleCopilotChatAuth(run.chatId, authenticatedUserId, { principal }) + ? await getAccessibleCopilotChatForCancellation(run.chatId, authenticatedUserId, { + principal, + }) : null if (run.chatId && !chat) { return NextResponse.json({ error: 'Stream not found' }, { status: 404 }) diff --git a/apps/sim/lib/api/contracts/copilot.ts b/apps/sim/lib/api/contracts/copilot.ts index 5b57bfb830b..62a6c0c2882 100644 --- a/apps/sim/lib/api/contracts/copilot.ts +++ b/apps/sim/lib/api/contracts/copilot.ts @@ -7,11 +7,15 @@ import { type AsyncConfirmationStatus, } from '@/lib/copilot/async-runs/lifecycle' import { + BILLING_ACCOUNT_DECISION_HEADER, + BILLING_ACCOUNT_DECISION_HEADER_MAX_BYTES, BILLING_ATTRIBUTION_HEADER, BILLING_ATTRIBUTION_HEADER_MAX_BYTES, BILLING_REQUEST_ID_HEADER, COPILOT_BILLING_PROTOCOL_HEADER, COPILOT_BILLING_PROTOCOL_VALUES, + COPILOT_VALIDATION_PURPOSE, + COPILOT_VALIDATION_PURPOSE_VALUES, } from '@/lib/copilot/generated/billing-protocol-v1' import { PERSISTED_RESOURCE_TYPES } from '@/lib/copilot/resources/types' @@ -255,6 +259,10 @@ export const deleteCopilotChatBodySchema = z.object({ export type DeleteCopilotChatBody = z.input export const validateCopilotApiKeyHeadersSchema = z.object({ + [BILLING_ACCOUNT_DECISION_HEADER]: z + .string() + .max(BILLING_ACCOUNT_DECISION_HEADER_MAX_BYTES) + .optional(), [COPILOT_BILLING_PROTOCOL_HEADER]: z.enum(COPILOT_BILLING_PROTOCOL_VALUES).optional(), [BILLING_REQUEST_ID_HEADER]: z.string().uuid().optional(), [BILLING_ATTRIBUTION_HEADER]: z.string().max(BILLING_ATTRIBUTION_HEADER_MAX_BYTES).optional(), @@ -271,6 +279,8 @@ export type ValidateCopilotApiKeyError = z.output ({ where: () => ({ limit }), innerJoin: () => ({ where: () => ({ limit }) }), + leftJoin: () => ({ where: () => ({ limit }) }), }), }), }, @@ -76,7 +77,6 @@ describe('prepareSessionForCreation', () => { limit.mockResolvedValueOnce([{ email: 'member@example.com', suspendedAt: null }]) limit.mockResolvedValueOnce([{ organizationId: 'org-1' }]) limit.mockResolvedValueOnce([{ settings: { maxSessionHours: 24 } }]) - limit.mockResolvedValueOnce([{ userId: 'owner-1' }]) limit.mockResolvedValueOnce([{ billingBlocked: false, billingBlockedReason: null }]) limit.mockResolvedValueOnce([{ plan: 'enterprise', status: 'active' }]) @@ -89,7 +89,7 @@ describe('prepareSessionForCreation', () => { expiresAt: new Date('2026-09-09T00:00:00Z'), }, }) - expect(limit).toHaveBeenCalledTimes(6) + expect(limit).toHaveBeenCalledTimes(5) expect(db.select).not.toHaveBeenCalled() }) diff --git a/apps/sim/lib/billing/calculations/usage-reservation.test.ts b/apps/sim/lib/billing/calculations/usage-reservation.test.ts index 8a020ce48e7..fbfd597f996 100644 --- a/apps/sim/lib/billing/calculations/usage-reservation.test.ts +++ b/apps/sim/lib/billing/calculations/usage-reservation.test.ts @@ -2,7 +2,9 @@ * @vitest-environment node */ import { redisConfigMockFns, resetEnvFlagsMock, setEnvFlags } from '@sim/testing' -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { generateId } from '@sim/utils/id' +import Redis from 'ioredis' +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' import { refreshExecutionSlotExpiry, releaseExecutionSlot, @@ -528,3 +530,99 @@ describe('usage-reservation', () => { }) }) }) + +const redisUrl = process.env.BILLING_USAGE_TEST_REDIS_URL +if (redisUrl) { + const target = new URL(redisUrl) + if ( + target.protocol !== 'redis:' || + !['localhost', '127.0.0.1', '[::1]'].includes(target.hostname) + ) { + throw new Error('Usage reservation integration tests require a disposable local Redis') + } +} + +describe.runIf(Boolean(redisUrl))('pooled usage reservations with Redis', () => { + let redis: Redis + const reservations: string[] = [] + const payer = { type: 'organization' as const, id: generateId() } + + beforeAll(async () => { + redis = new Redis(redisUrl!, { lazyConnect: true, maxRetriesPerRequest: 0 }) + await redis.connect() + }) + + beforeEach(() => { + setEnvFlags({ isHosted: true, isBillingEnabled: true }) + redisConfigMockFns.mockGetRedisClient.mockReturnValue(redis) + }) + + afterEach(async () => { + await Promise.all(reservations.splice(0).map(releaseExecutionSlot)) + }) + + afterAll(async () => { + await redis?.quit() + }) + + function params(actorUserId = generateId()) { + const reservationId = generateId() + reservations.push(reservationId) + return { + billingEntity: payer, + reservationId, + plan: 'enterprise' as const, + currentUsage: 0, + limit: 0.05, + member: { organizationId: payer.id, actorUserId, currentUsage: 0, limit: 0.01 }, + } + } + + it('shares payer headroom across 100 concurrent requests from different members', async () => { + const requests = Array.from({ length: 100 }, () => params()) + const results = await Promise.all(requests.map(reserveExecutionSlot)) + expect(results.filter((result) => result.reserved)).toHaveLength(10) + expect(results.filter((result) => !result.reserved)).toEqual( + Array.from({ length: 90 }, () => ({ reserved: false, reason: 'payer_headroom' })) + ) + expect( + await reserveExecutionSlot({ + ...params(), + billingEntity: { type: 'organization', id: generateId() }, + member: undefined, + }) + ).toEqual({ reserved: true, created: true }) + }) + + it('isolates member caps inside the shared payer without consuming rejected slots', async () => { + const memberA = generateId() + const memberB = generateId() + const results = await Promise.all( + Array.from({ length: 40 }, (_, index) => + reserveExecutionSlot(params(index < 20 ? memberA : memberB)) + ) + ) + expect(results.slice(0, 20).filter((result) => result.reserved)).toHaveLength(2) + expect(results.slice(20).filter((result) => result.reserved)).toHaveLength(2) + expect(results.filter((result) => !result.reserved)).toEqual( + Array.from({ length: 36 }, () => ({ reserved: false, reason: 'member_headroom' })) + ) + expect(await reserveExecutionSlot(params())).toEqual({ reserved: true, created: true }) + }) + + it('preserves duplicate ownership and queued-worker refresh without new admission', async () => { + const request = params() + const results = await Promise.all( + Array.from({ length: 20 }, () => reserveExecutionSlot(request)) + ) + expect(results.filter((result) => result.reserved && result.created)).toHaveLength(1) + expect(results.every((result) => result.reserved)).toBe(true) + expect(await refreshExecutionSlotExpiry(request.reservationId, Date.now() + 60_000)).toBe(true) + await releaseExecutionSlot(request.reservationId) + expect(await refreshExecutionSlotExpiry(request.reservationId, Date.now() + 60_000)).toBe(false) + expect(await reserveExecutionSlot({ ...params(), currentUsage: 0.05 })).toEqual({ + reserved: false, + reason: 'payer_headroom', + }) + }) +}) diff --git a/apps/sim/lib/billing/core/access.test.ts b/apps/sim/lib/billing/core/access.test.ts index 9ae1dee42c9..42acfbd867f 100644 --- a/apps/sim/lib/billing/core/access.test.ts +++ b/apps/sim/lib/billing/core/access.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment node */ -import { queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' +import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import { getBillingEntityBlockStatus, getEffectiveBillingStatus } from '@/lib/billing/core/access' @@ -119,8 +119,9 @@ describe('getBillingEntityBlockStatus', () => { * block this organization's workspaces. */ it("reads the owner's own row and stops there", async () => { - queueTableRows(schemaMock.member, [{ userId: 'owner-1' }]) - queueTableRows(schemaMock.userStats, [{ billingBlocked: false, billingBlockedReason: null }]) + queueTableRows(schemaMock.member, [ + { userId: 'owner-1', billingBlocked: false, billingBlockedReason: null }, + ]) queueTableRows(schemaMock.member, [{ organizationId: 'unrelated-org' }]) queueTableRows(schemaMock.userStats, [{ blocked: true, blockedReason: 'dispute' }]) @@ -130,22 +131,31 @@ describe('getBillingEntityBlockStatus', () => { billingBlocked: false, billingBlockedReason: null, }) - }) - - it("blocks when the owner's own row is blocked", async () => { - queueTableRows(schemaMock.member, [{ userId: 'owner-1' }]) - queueTableRows(schemaMock.userStats, [ - { billingBlocked: true, billingBlockedReason: 'payment_failed' }, - ]) - - await expect( - getBillingEntityBlockStatus({ type: 'organization', id: 'org-1' }) - ).resolves.toEqual({ - billingBlocked: true, - billingBlockedReason: 'payment_failed', + expect(dbChainMockFns.select).toHaveBeenCalledTimes(1) + expect(dbChainMockFns.leftJoin).toHaveBeenCalledWith(schemaMock.userStats, { + type: 'eq', + left: schemaMock.userStats.userId, + right: schemaMock.member.userId, }) }) + it.each(['payment_failed', 'dispute'])( + "blocks when the owner's own row is blocked for %s", + async (reason) => { + queueTableRows(schemaMock.member, [ + { userId: 'owner-1', billingBlocked: true, billingBlockedReason: reason }, + ]) + + await expect( + getBillingEntityBlockStatus({ type: 'organization', id: 'org-1' }) + ).resolves.toEqual({ + billingBlocked: true, + billingBlockedReason: reason, + }) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(1) + } + ) + it('is not blocked when the organization has no owner row', async () => { queueTableRows(schemaMock.member, []) @@ -155,6 +165,41 @@ describe('getBillingEntityBlockStatus', () => { billingBlocked: false, billingBlockedReason: null, }) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(1) + }) + + it('preserves the unblocked result when the owner has no stats row', async () => { + queueTableRows(schemaMock.member, [ + { userId: 'owner-1', billingBlocked: null, billingBlockedReason: null }, + ]) + await expect( + getBillingEntityBlockStatus({ type: 'organization', id: 'org-1' }) + ).resolves.toEqual({ billingBlocked: false, billingBlockedReason: null }) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(1) + }) + + it('reads a changed payer block on the next call', async () => { + queueTableRows(schemaMock.member, [ + { userId: 'owner-1', billingBlocked: false, billingBlockedReason: 'dispute' }, + ]) + queueTableRows(schemaMock.member, [ + { userId: 'owner-1', billingBlocked: true, billingBlockedReason: 'dispute' }, + ]) + await expect( + getBillingEntityBlockStatus({ type: 'organization', id: 'org-1' }) + ).resolves.toEqual({ billingBlocked: false, billingBlockedReason: null }) + await expect( + getBillingEntityBlockStatus({ type: 'organization', id: 'org-1' }) + ).resolves.toEqual({ billingBlocked: true, billingBlockedReason: 'dispute' }) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(2) + }) + + it('propagates a failed payer-status read', async () => { + const failure = new Error('database unavailable') + dbChainMockFns.limit.mockRejectedValueOnce(failure) + await expect(getBillingEntityBlockStatus({ type: 'organization', id: 'org-1' })).rejects.toBe( + failure + ) }) }) }) diff --git a/apps/sim/lib/billing/core/access.ts b/apps/sim/lib/billing/core/access.ts index c9917d69991..07832f99ecc 100644 --- a/apps/sim/lib/billing/core/access.ts +++ b/apps/sim/lib/billing/core/access.ts @@ -17,35 +17,6 @@ export interface BillingEntityBlockStatus { billingBlockedReason: 'payment_failed' | 'dispute' | null } -/** - * Reads one user's own `user_stats` row, without re-deriving the block that - * their organization membership would imply. - * - * Only the organization branch of {@link getBillingEntityBlockStatus} wants this - * narrow read: an organization's debt is its owner's own debt, and some other - * org the owner merely belongs to is not this organization's problem. Callers - * asking whether a *user* is blocked want {@link getEffectiveBillingStatus}. - */ -async function getUserStatsBlockStatus( - userId: string, - executor: DbOrTx -): Promise { - const [stats] = await executor - .select({ - billingBlocked: userStats.billingBlocked, - billingBlockedReason: userStats.billingBlockedReason, - }) - .from(userStats) - .where(eq(userStats.userId, userId)) - .limit(1) - - const billingBlocked = Boolean(stats?.billingBlocked) - return { - billingBlocked, - billingBlockedReason: billingBlocked ? (stats?.billingBlockedReason ?? null) : null, - } -} - /** * Reads the effective block state of one payer, personal or organization. * @@ -72,8 +43,12 @@ export async function getBillingEntityBlockStatus( } const [owner] = await executor - .select({ userId: member.userId }) + .select({ + billingBlocked: userStats.billingBlocked, + billingBlockedReason: userStats.billingBlockedReason, + }) .from(member) + .leftJoin(userStats, eq(userStats.userId, member.userId)) .where(and(eq(member.organizationId, billingEntity.id), eq(member.role, 'owner'))) .limit(1) @@ -85,7 +60,11 @@ export async function getBillingEntityBlockStatus( return { billingBlocked: false, billingBlockedReason: null } } - return getUserStatsBlockStatus(owner.userId, executor) + const billingBlocked = Boolean(owner.billingBlocked) + return { + billingBlocked, + billingBlockedReason: billingBlocked ? (owner.billingBlockedReason ?? null) : null, + } } /** diff --git a/apps/sim/lib/billing/core/usage-log.postgres.test.ts b/apps/sim/lib/billing/core/usage-log.postgres.test.ts index 590ece0a4b8..01a61c3d062 100644 --- a/apps/sim/lib/billing/core/usage-log.postgres.test.ts +++ b/apps/sim/lib/billing/core/usage-log.postgres.test.ts @@ -29,6 +29,8 @@ vi.mock('@/lib/billing/subscriptions/utils', () => ({ isOrgScopedSubscription: v import { CumulativeUsageContextMismatchError, + getBillingPeriodUsageCost, + getBillingPeriodUsageCostByUser, type RecordCumulativeUsageParams, recordCumulativeUsage, } from '@/lib/billing/core/usage-log' @@ -258,6 +260,54 @@ describe.skipIf(!databaseUrl)('Cumulative billing with PostgreSQL', () => { expect(await recordCumulativeUsage(usage(0.8))).toEqual({ billed: false, delta: 0, total: 0.8 }) }) + it('reads committed pooled and member charges freshly after concurrent executions', async () => { + if (!database) throw new Error('PostgreSQL fixture is unavailable') + const { billingEntity, billingPeriod } = usage(0) + if (!billingEntity || !billingPeriod) throw new Error('Billing fixture scope is missing') + const readPool = () => + getBillingPeriodUsageCost(billingEntity, billingPeriod, undefined, database) + expect(await readPool()).toBe(0) + await Promise.all( + Array.from({ length: 64 }, (_, index) => + recordCumulativeUsage({ + ...usage(0.005, `concurrent:${index}`), + userId: `member-${index % 4}`, + }) + ) + ) + expect(await readPool()).toBeCloseTo(0.32, 9) + const members = await getBillingPeriodUsageCostByUser( + billingEntity, + billingPeriod, + undefined, + database + ) + expect(members).toEqual( + new Map(Array.from({ length: 4 }, (_, index) => [`member-${index}`, 0.08])) + ) + await recordCumulativeUsage({ ...usage(0.105, 'concurrent:0'), userId: 'member-0' }) + expect(await readPool()).toBeCloseTo(0.42, 9) + expect( + await getBillingPeriodUsageCost( + { type: 'organization', id: 'other-payer' }, + billingPeriod, + undefined, + database + ) + ).toBe(0) + expect( + await getBillingPeriodUsageCost( + billingEntity, + { + start: billingPeriod.end, + end: new Date('2026-11-01T00:00:00.000Z'), + }, + undefined, + database + ) + ).toBe(0) + }) + it.each([0.2, 0.8])( 'rejects an actor mismatch even for a non-increasing callback (%s)', async (cost) => { diff --git a/apps/sim/lib/billing/core/usage.test.ts b/apps/sim/lib/billing/core/usage.test.ts index 7ce58ed8214..7ced9be6ab4 100644 --- a/apps/sim/lib/billing/core/usage.test.ts +++ b/apps/sim/lib/billing/core/usage.test.ts @@ -106,6 +106,7 @@ vi.mock('@/lib/messaging/email/unsubscribe', () => ({ vi.mock('@sim/platform-authz/workspace', () => ({ isOrgAdminRole: mockIsOrgAdminRole })) import { + getOrgUsageLimit, getUserUsageLimit, maybeSendUsageThresholdEmail, syncUsageLimitsFromSubscription, @@ -200,6 +201,101 @@ describe('getUserUsageLimit', () => { await expect(getUserUsageLimit('user-1', null)).resolves.toBe(10) }) + + it.each([ + { plan: 'enterprise', configured: '12.005', seats: 3, expected: 12.005 }, + { plan: 'enterprise', configured: '0', seats: 3, expected: 0 }, + { plan: 'enterprise', configured: null, seats: 3, expected: 0 }, + { plan: 'team', configured: '10', seats: 3, expected: 60 }, + { plan: 'team', configured: '80', seats: 3, expected: 80 }, + { plan: 'team', configured: null, seats: 0, expected: 20 }, + ])( + 'reads the $plan organization limit once for configured=$configured and seats=$seats', + async ({ plan, configured, seats, expected }) => { + mockIsOrgScopedSubscription.mockReturnValue(true) + queueTableRows(schemaMock.organization, [{ orgUsageLimit: configured }]) + await expect( + getUserUsageLimit('user-1', { + referenceId: 'org-1', + plan, + seats, + status: 'active', + periodStart: null, + periodEnd: null, + }) + ).resolves.toBe(expected) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(1) + expect(dbChainMockFns.update).not.toHaveBeenCalled() + } + ) + + it('still rejects a missing organization without adopting the display fallback', async () => { + mockIsOrgScopedSubscription.mockReturnValue(true) + queueTableRows(schemaMock.organization, []) + await expect( + getUserUsageLimit('user-1', { + referenceId: 'org-missing', + plan: 'team', + seats: 3, + status: 'active', + periodStart: null, + periodEnd: null, + }) + ).rejects.toThrow('Organization not found: org-missing for user: user-1') + expect(dbChainMockFns.select).toHaveBeenCalledTimes(1) + }) + + it('does not retain an organization cap between calls', async () => { + mockIsOrgScopedSubscription.mockReturnValue(true) + const subscription = { + referenceId: 'org-1', + plan: 'enterprise', + seats: 1, + status: 'active', + periodStart: null, + periodEnd: null, + } + queueTableRows(schemaMock.organization, [{ orgUsageLimit: '50' }]) + queueTableRows(schemaMock.organization, [{ orgUsageLimit: '20' }]) + await expect(getUserUsageLimit('user-1', subscription)).resolves.toBe(50) + await expect(getUserUsageLimit('user-1', subscription)).resolves.toBe(20) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(2) + }) + + it('propagates a failed organization limit read', async () => { + mockIsOrgScopedSubscription.mockReturnValue(true) + const failure = new Error('database unavailable') + dbChainMockFns.limit.mockRejectedValueOnce(failure) + await expect( + getUserUsageLimit('user-1', { + referenceId: 'org-1', + plan: 'team', + seats: 3, + status: 'active', + periodStart: null, + periodEnd: null, + }) + ).rejects.toBe(failure) + }) +}) + +describe('getOrgUsageLimit', () => { + beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + }) + + it.each([ + { plan: 'team', expected: { limit: 60, minimum: 60 } }, + { plan: 'enterprise', expected: { limit: 0, minimum: 0 } }, + ])( + 'preserves the public $plan fallback for a missing organization', + async ({ plan, expected }) => { + queueTableRows(schemaMock.organization, []) + await expect(getOrgUsageLimit('org-missing', plan, 3)).resolves.toEqual(expected) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(1) + } + ) }) describe('syncUsageLimitsFromSubscription', () => { diff --git a/apps/sim/lib/billing/core/usage.ts b/apps/sim/lib/billing/core/usage.ts index 6231b7d81d2..b62fe0c8bd9 100644 --- a/apps/sim/lib/billing/core/usage.ts +++ b/apps/sim/lib/billing/core/usage.ts @@ -111,17 +111,40 @@ export async function getOrgUsageLimit( seats: number | null, executor: DbClient = db ): Promise { - const orgData = await executor + return ( + (await findOrgUsageLimit(organizationId, plan, seats, executor)) ?? + calculateOrgUsageLimit(organizationId, plan, seats, null) + ) +} + +async function findOrgUsageLimit( + organizationId: string, + plan: string, + seats: number | null, + executor: DbClient = db +): Promise { + const [orgData] = await executor .select({ orgUsageLimit: organization.orgUsageLimit }) .from(organization) .where(eq(organization.id, organizationId)) .limit(1) - const configured = - orgData.length > 0 && orgData[0].orgUsageLimit - ? toNumber(toDecimal(orgData[0].orgUsageLimit)) - : null + if (!orgData) return null + + return calculateOrgUsageLimit( + organizationId, + plan, + seats, + orgData.orgUsageLimit ? toNumber(toDecimal(orgData.orgUsageLimit)) : null + ) +} +function calculateOrgUsageLimit( + organizationId: string, + plan: string, + seats: number | null, + configured: number | null +): OrgUsageLimitResult { if (isEnterprise(plan)) { // Enterprise: Use configured limit directly (no per-seat minimum) if (configured !== null) { @@ -466,21 +489,16 @@ export async function getUserUsageLimit( : await getHighestPrioritySubscription(userId) if (isOrgScopedSubscription(subscription, userId) && subscription) { - const orgExists = await db - .select({ id: organization.id }) - .from(organization) - .where(eq(organization.id, subscription.referenceId)) - .limit(1) - - if (orgExists.length === 0) { - throw new Error(`Organization not found: ${subscription.referenceId} for user: ${userId}`) - } - - const orgLimit = await getOrgUsageLimit( + const orgLimit = await findOrgUsageLimit( subscription.referenceId, subscription.plan, subscription.seats ) + + if (!orgLimit) { + throw new Error(`Organization not found: ${subscription.referenceId} for user: ${userId}`) + } + return orgLimit.limit } diff --git a/apps/sim/lib/copilot/application/authorize-chat-callback.test.ts b/apps/sim/lib/copilot/application/authorize-chat-callback.test.ts new file mode 100644 index 00000000000..1c9af16437a --- /dev/null +++ b/apps/sim/lib/copilot/application/authorize-chat-callback.test.ts @@ -0,0 +1,224 @@ +/** @vitest-environment node */ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { + AccountBillingDecision, + BillingAttributionSnapshot, +} from '@/lib/billing/core/billing-attribution' +import { + authorizeCopilotChatCallback, + checkCopilotContinuationBilling, +} from '@/lib/copilot/application/authorize-chat-callback' +import { OrchestrationError } from '@/lib/core/orchestration/types' + +const mocks = vi.hoisted(() => ({ + loadWorkspace: vi.fn(), + permission: vi.fn(), + capability: vi.fn(), + organization: vi.fn(), + attributedBlocks: vi.fn(), + actorBlock: vi.fn(), + payerBlock: vi.fn(), +})) +vi.mock('@/lib/workspaces/application/workspace-context', () => ({ + resolveActiveWorkspaceApplicationContext: mocks.loadWorkspace, +})) +vi.mock('@sim/platform-authz/workspace', async (importOriginal) => ({ + ...(await importOriginal()), + resolveEffectiveWorkspacePermission: mocks.permission, +})) +vi.mock('@/lib/permission-groups/capability-assertions', async (importOriginal) => ({ + ...(await importOriginal()), + assertWorkspaceCapability: mocks.capability, +})) +vi.mock('@/lib/copilot/chat/organization-chats', () => ({ + authorizeOrganizationChatDelegation: { execute: mocks.organization }, +})) +vi.mock('@/lib/billing/core/billing-attribution', () => ({ + checkAttributedBillingBlocks: mocks.attributedBlocks, +})) +vi.mock('@/lib/billing/calculations/usage-monitor', () => ({ + checkBillingBlocked: mocks.actorBlock, + checkBillingEntityBlocked: mocks.payerBlock, +})) + +const context = { + userId: 'actor', + workspaceId: 'workspace', + chatId: 'chat', + delegationId: 'request', + purpose: 'continuation' as const, +} +const account: AccountBillingDecision = { + userId: 'actor', + billingEntity: { type: 'organization', id: 'original-payer' }, + billingPeriod: { start: '2026-07-01T00:00:00.000Z', end: '2026-08-01T00:00:00.000Z' }, +} +const attribution: BillingAttributionSnapshot = { + actorUserId: 'actor', + billedAccountUserId: 'owner', + workspaceId: 'workspace', + organizationId: 'original-payer', + billingEntity: account.billingEntity, + billingPeriod: account.billingPeriod, + payerSubscription: null, +} + +beforeEach(() => { + vi.resetAllMocks() + mocks.loadWorkspace.mockResolvedValue({ + workspaceId: 'workspace', + workspaceOrganizationId: 'current-organization', + allowPersonalApiKeys: true, + billedAccountUserId: 'new-owner', + }) + mocks.permission.mockResolvedValue('read') + mocks.organization.mockResolvedValue(undefined) + mocks.actorBlock.mockResolvedValue({ blocked: false }) + mocks.payerBlock.mockResolvedValue({ blocked: false }) + mocks.attributedBlocks.mockResolvedValue({ blocked: false }) +}) + +describe('fresh chat callback authorization', () => { + it('checks the actor current membership and capability in the canonical workspace', async () => { + await authorizeCopilotChatCallback(context) + expect(mocks.loadWorkspace).toHaveBeenCalledWith('workspace') + expect(mocks.permission).toHaveBeenCalledWith( + 'actor', + 'workspace', + 'current-organization', + undefined, + { forUpdate: undefined } + ) + expect(mocks.capability).toHaveBeenCalledWith( + 'actor', + 'workspace', + 'copilot.use', + 'current-organization' + ) + expect(mocks.permission.mock.invocationCallOrder[0]).toBeLessThan( + mocks.capability.mock.invocationCallOrder[0] + ) + }) + + it.each(['continuation', 'cancellation'] as const)( + 'rejects removed membership on %s', + async (purpose) => { + mocks.permission.mockResolvedValueOnce(null) + await expect(authorizeCopilotChatCallback({ ...context, purpose })).rejects.toMatchObject({ + code: 'forbidden', + }) + expect(mocks.capability).not.toHaveBeenCalled() + } + ) + + it.each(['continuation', 'cancellation'] as const)( + 'rejects an archived or removed workspace on %s', + async (purpose) => { + mocks.loadWorkspace.mockRejectedValueOnce( + new OrchestrationError('not_found', 'Workspace not found') + ) + await expect(authorizeCopilotChatCallback({ ...context, purpose })).rejects.toMatchObject({ + code: 'not_found', + }) + expect(mocks.permission).not.toHaveBeenCalled() + } + ) + + it('rejects continuation after capability revocation, but allows the actor to stop', async () => { + mocks.capability.mockRejectedValue(new OrchestrationError('forbidden', 'Copilot disabled')) + await expect(authorizeCopilotChatCallback(context)).rejects.toMatchObject({ code: 'forbidden' }) + mocks.capability.mockClear() + await authorizeCopilotChatCallback({ ...context, purpose: 'cancellation' }) + expect(mocks.permission).toHaveBeenCalledTimes(2) + expect(mocks.capability).not.toHaveBeenCalled() + }) + + it('fails closed if canonical workspace scope changes unexpectedly', async () => { + mocks.loadWorkspace.mockResolvedValueOnce({ + workspaceId: 'other-workspace', + workspaceOrganizationId: null, + allowPersonalApiKeys: true, + }) + await expect(authorizeCopilotChatCallback(context)).rejects.toMatchObject({ code: 'forbidden' }) + expect(mocks.permission).not.toHaveBeenCalled() + }) + + it('propagates membership infrastructure failures', async () => { + mocks.permission.mockRejectedValueOnce(new Error('database unavailable')) + await expect(authorizeCopilotChatCallback(context)).rejects.toThrow('database unavailable') + }) + + it.each([ + ['continuation', 'sim:copilot-billing'], + ['cancellation', 'sim:copilot-cancel'], + ] as const)( + 'reauthorizes the original private organization chat for %s', + async (purpose, audience) => { + await authorizeCopilotChatCallback({ + ...context, + workspaceId: undefined, + organizationId: 'org', + purpose, + }) + expect(mocks.organization).toHaveBeenCalledWith({ + principal: expect.objectContaining({ + kind: 'organization_delegated', + subjectUserId: 'actor', + organizationId: 'org', + audience, + resourceScope: { chatId: 'chat' }, + }), + }) + expect(mocks.loadWorkspace).not.toHaveBeenCalled() + } + ) + + it.each([ + { workspaceId: undefined, organizationId: 'org', chatId: undefined }, + { workspaceId: 'workspace', organizationId: 'org', chatId: 'chat' }, + ])('refuses invalid organization scope %s', async (scope) => { + await expect(authorizeCopilotChatCallback({ ...context, ...scope })).rejects.toMatchObject({ + code: 'forbidden', + }) + expect(mocks.organization).not.toHaveBeenCalled() + }) +}) + +describe('continuation account standing', () => { + it('uses the existing attributed block policy with the original snapshot', async () => { + await checkCopilotContinuationBilling({ kind: 'attributed', attribution }) + expect(mocks.attributedBlocks).toHaveBeenCalledWith(attribution) + expect(mocks.actorBlock).not.toHaveBeenCalled() + expect(mocks.payerBlock).not.toHaveBeenCalled() + }) + + it('checks both actor and the exact original direct-account payer', async () => { + await checkCopilotContinuationBilling({ kind: 'account', decision: account }) + expect(mocks.actorBlock).toHaveBeenCalledWith('actor') + expect(mocks.payerBlock).toHaveBeenCalledWith({ type: 'organization', id: 'original-payer' }) + }) + + it('refuses an actor block before reading the payer', async () => { + mocks.actorBlock.mockResolvedValueOnce({ blocked: true }) + await expect( + checkCopilotContinuationBilling({ kind: 'account', decision: account }) + ).resolves.toMatchObject({ blocked: true, scope: 'actor' }) + expect(mocks.payerBlock).not.toHaveBeenCalled() + }) + + it('refuses a payer block independently of actor standing', async () => { + mocks.payerBlock.mockResolvedValueOnce({ blocked: true }) + await expect( + checkCopilotContinuationBilling({ kind: 'account', decision: account }) + ).resolves.toMatchObject({ blocked: true, scope: 'payer' }) + }) + + it('reads the same personal actor/payer only once', async () => { + await checkCopilotContinuationBilling({ + kind: 'account', + decision: { ...account, billingEntity: { type: 'user', id: 'actor' } }, + }) + expect(mocks.actorBlock).toHaveBeenCalledTimes(1) + expect(mocks.payerBlock).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/lib/copilot/application/authorize-chat-callback.ts b/apps/sim/lib/copilot/application/authorize-chat-callback.ts new file mode 100644 index 00000000000..58774447753 --- /dev/null +++ b/apps/sim/lib/copilot/application/authorize-chat-callback.ts @@ -0,0 +1,107 @@ +import type { DelegatedPrincipal } from '@sim/auth/principal' +import { + checkBillingBlocked, + checkBillingEntityBlocked, +} from '@/lib/billing/calculations/usage-monitor' +import { + type AccountBillingDecision, + type BillingAttributionSnapshot, + checkAttributedBillingBlocks, +} from '@/lib/billing/core/billing-attribution' +import { chatOperations } from '@/lib/copilot/application/operations' +import { + COPILOT_APPLICATION_DELEGATION_TTL_MS, + createTrustedCopilotPrincipal, + createTrustedOrganizationCopilotPrincipal, +} from '@/lib/copilot/auth/application-delegation' +import { authorizeOrganizationChatDelegation } from '@/lib/copilot/chat/organization-chats' +import { + COPILOT_VALIDATION_PURPOSE, + type CopilotValidationPurpose, +} from '@/lib/copilot/generated/billing-protocol-v1' +import { defineAuthorizedWorkspaceUseCase } from '@/lib/core/application/authorized-workspace-use-case' +import { OrchestrationError } from '@/lib/core/orchestration/types' +import { resolveActiveWorkspaceApplicationContext } from '@/lib/workspaces/application/workspace-context' + +const CALLBACK_AUDIENCE = 'sim:copilot-callback' + +interface WorkspaceCallbackInput { + workspaceId: string +} + +const workspaceCallbackAuthorization = { + resolveContext: ({ input }: { input: WorkspaceCallbackInput }) => + resolveActiveWorkspaceApplicationContext(input.workspaceId), + authorizationOptions: { + delegation: { + audience: CALLBACK_AUDIENCE, + isWithinScope: (principal: DelegatedPrincipal) => Boolean(principal.subjectUserId), + }, + }, + async execute() {}, +} + +const continueWorkspaceChat = defineAuthorizedWorkspaceUseCase({ + operation: chatOperations.continue, + ...workspaceCallbackAuthorization, +}) +const cancelWorkspaceChat = defineAuthorizedWorkspaceUseCase({ + operation: chatOperations.cancel, + ...workspaceCallbackAuthorization, +}) + +interface CopilotChatCallbackContext { + userId: string + workspaceId?: string + organizationId?: string + chatId?: string + delegationId: string + purpose: Exclude +} + +/** Reauthorizes the original server-owned scope across a Go lifecycle boundary. */ +export async function authorizeCopilotChatCallback(context: CopilotChatCallbackContext) { + if (context.organizationId) { + if (!context.chatId || context.workspaceId) { + throw new OrchestrationError('forbidden', 'Invalid conversation scope') + } + const principal = createTrustedOrganizationCopilotPrincipal( + { ...context, organizationId: context.organizationId, chatId: context.chatId }, + { + audience: + context.purpose === COPILOT_VALIDATION_PURPOSE.cancellation + ? 'sim:copilot-cancel' + : 'sim:copilot-billing', + ttlMs: COPILOT_APPLICATION_DELEGATION_TTL_MS, + } + ) + await authorizeOrganizationChatDelegation.execute({ principal }) + return + } + if (!context.workspaceId) return + + const principal = createTrustedCopilotPrincipal( + { ...context, workspaceId: context.workspaceId }, + { audience: CALLBACK_AUDIENCE, ttlMs: COPILOT_APPLICATION_DELEGATION_TTL_MS } + ) + const useCase = + context.purpose === COPILOT_VALIDATION_PURPOSE.cancellation + ? cancelWorkspaceChat + : continueWorkspaceChat + await useCase.execute({ principal, input: { workspaceId: context.workspaceId } }) +} + +export type CopilotContinuationBilling = + | { kind: 'attributed'; attribution: BillingAttributionSnapshot } + | { kind: 'account'; decision: AccountBillingDecision } + +/** Checks account standing against the original admission; never reads spend or selects a new payer. */ +export async function checkCopilotContinuationBilling(billing: CopilotContinuationBilling) { + if (billing.kind === 'attributed') return checkAttributedBillingBlocks(billing.attribution) + + const actor = await checkBillingBlocked(billing.decision.userId) + if (actor.blocked) return { ...actor, scope: 'actor' } + const payer = billing.decision.billingEntity + if (payer.type === 'user' && payer.id === billing.decision.userId) return actor + return { ...(await checkBillingEntityBlocked(payer)), scope: 'payer' } +} diff --git a/apps/sim/lib/copilot/application/operations.ts b/apps/sim/lib/copilot/application/operations.ts index d152c1c54b9..7051af5b838 100644 --- a/apps/sim/lib/copilot/application/operations.ts +++ b/apps/sim/lib/copilot/application/operations.ts @@ -7,6 +7,25 @@ import { defineWorkspaceOperation } from '@/lib/core/application/workspace-opera * silently substituting the key's owner. */ export const chatOperations = { + continue: defineWorkspaceOperation({ + id: 'chat.continue', + minimumRole: 'read', + workspaceApiKey: 'deny', + capability: 'copilot.use', + principalKinds: ['delegated'], + delegatedServices: ['copilot'], + }), + /** + * permission-group-exempt: Stopping existing work remains available after Copilot is disabled. + */ + cancel: defineWorkspaceOperation({ + id: 'chat.cancel', + minimumRole: 'read', + workspaceApiKey: 'deny', + capability: 'none', + principalKinds: ['delegated'], + delegatedServices: ['copilot'], + }), send: defineWorkspaceOperation({ id: 'chat.send', oauthScope: 'api:write', diff --git a/apps/sim/lib/copilot/chat/lifecycle.test.ts b/apps/sim/lib/copilot/chat/lifecycle.test.ts index 84415ef99e1..f5eed98acf6 100644 --- a/apps/sim/lib/copilot/chat/lifecycle.test.ts +++ b/apps/sim/lib/copilot/chat/lifecycle.test.ts @@ -3,6 +3,8 @@ */ import { dbChainMockFns, resetDbChainMock, schemaMock, workflowAuthzMockFns } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { createTrustedOrganizationCopilotPrincipal } from '@/lib/copilot/auth/application-delegation' +import { OrchestrationError } from '@/lib/core/orchestration/types' const { mockAuthorizeWorkflowByWorkspacePermission: mockAuthorizeWorkflow, @@ -14,9 +16,13 @@ afterAll(() => { mockGetActiveWorkflow.mockReset() }) -const { mockAuthorizeOrganization } = vi.hoisted(() => ({ mockAuthorizeOrganization: vi.fn() })) +const { mockAuthorizeOrganization, mockAuthorizeCancellation } = vi.hoisted(() => ({ + mockAuthorizeOrganization: vi.fn(), + mockAuthorizeCancellation: vi.fn(), +})) vi.mock('@/lib/copilot/chat/organization-chats', () => ({ authorizeOrganizationChat: { execute: mockAuthorizeOrganization }, + authorizeOrganizationChatCancellation: { execute: mockAuthorizeCancellation }, })) vi.mock('@/lib/workspaces/permissions/utils', () => ({ @@ -26,6 +32,8 @@ vi.mock('@/lib/workspaces/permissions/utils', () => ({ import { getAccessibleCopilotChat, + getAccessibleCopilotChatAuth, + getAccessibleCopilotChatForCancellation, getAccessibleCopilotChatWithMessages, resolveOrCreateChat, } from '@/lib/copilot/chat/lifecycle' @@ -336,3 +344,99 @@ describe('organization chat isolation', () => { expect(dbChainMockFns.values).not.toHaveBeenCalled() }) }) + +describe('owned chat cancellation policy', () => { + const orgChat = { ...chatRow, organizationId: 'org-1', type: 'mothership' } + beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + mockAuthorizeOrganization.mockReset().mockResolvedValue(undefined) + mockAuthorizeCancellation.mockReset().mockResolvedValue(undefined) + }) + + it('allows stopping an owned org chat after capability revocation while ordinary reads remain denied', async () => { + mockAuthorizeOrganization.mockRejectedValue( + new OrchestrationError('forbidden', 'Copilot disabled') + ) + dbChainMockFns.limit.mockResolvedValueOnce([orgChat]).mockResolvedValueOnce([orgChat]) + expect( + await getAccessibleCopilotChatForCancellation(CHAT_ID, USER_ID, { principal: orgPrincipal }) + ).toEqual(orgChat) + expect(mockAuthorizeCancellation).toHaveBeenCalledWith({ + principal: orgPrincipal, + input: { organizationId: 'org-1' }, + }) + expect(mockAuthorizeOrganization).not.toHaveBeenCalled() + expect( + await getAccessibleCopilotChatAuth(CHAT_ID, USER_ID, { principal: orgPrincipal }) + ).toBeNull() + expect(mockAuthorizeOrganization).toHaveBeenCalledTimes(1) + expect(dbChainMockFns.orderBy).not.toHaveBeenCalled() + }) + + it('keeps the owned-live-chat predicate on cancellation', async () => { + dbChainMockFns.limit.mockResolvedValueOnce([orgChat]) + await getAccessibleCopilotChatForCancellation(CHAT_ID, USER_ID, { principal: orgPrincipal }) + const predicate = dbChainMockFns.where.mock.calls[0][0] as { conditions: unknown[] } + expect(predicate.conditions).toEqual([ + { type: 'eq', left: schemaMock.copilotChats.id, right: CHAT_ID }, + { type: 'eq', left: schemaMock.copilotChats.userId, right: USER_ID }, + { type: 'isNull', column: schemaMock.copilotChats.deletedAt }, + ]) + }) + + it('denies cancellation after organization membership removal', async () => { + dbChainMockFns.limit.mockResolvedValueOnce([orgChat]) + mockAuthorizeCancellation.mockRejectedValueOnce( + new OrchestrationError('not_found', 'Organization not found') + ) + expect( + await getAccessibleCopilotChatForCancellation(CHAT_ID, USER_ID, { principal: orgPrincipal }) + ).toBeNull() + expect(mockAuthorizeOrganization).not.toHaveBeenCalled() + }) + + it.each([undefined, { ...orgPrincipal, userId: 'other-user' }])( + 'denies cancellation without the matching actor principal', + async (principal) => { + dbChainMockFns.limit.mockResolvedValueOnce([orgChat]) + expect( + await getAccessibleCopilotChatForCancellation(CHAT_ID, USER_ID, { principal }) + ).toBeNull() + expect(mockAuthorizeCancellation).not.toHaveBeenCalled() + } + ) + + it('does not let delegated cancellation switch to another chat owned by the same actor', async () => { + dbChainMockFns.limit.mockResolvedValueOnce([orgChat]) + const principal = createTrustedOrganizationCopilotPrincipal( + { + userId: USER_ID, + organizationId: 'org-1', + chatId: 'other-chat', + delegationId: 'request', + }, + { audience: 'sim:copilot-cancel', ttlMs: 60000 } + ) + expect( + await getAccessibleCopilotChatForCancellation(CHAT_ID, USER_ID, { principal }) + ).toBeNull() + expect(mockAuthorizeCancellation).not.toHaveBeenCalled() + }) + + it('denies missing/deleted/non-owned chats before authorizing cancellation', async () => { + dbChainMockFns.limit.mockResolvedValueOnce([]) + expect( + await getAccessibleCopilotChatForCancellation(CHAT_ID, USER_ID, { principal: orgPrincipal }) + ).toBeNull() + expect(mockAuthorizeCancellation).not.toHaveBeenCalled() + }) + + it('propagates cancellation authorization infrastructure errors', async () => { + dbChainMockFns.limit.mockResolvedValueOnce([orgChat]) + mockAuthorizeCancellation.mockRejectedValueOnce(new Error('database unavailable')) + await expect( + getAccessibleCopilotChatForCancellation(CHAT_ID, USER_ID, { principal: orgPrincipal }) + ).rejects.toThrow('database unavailable') + }) +}) diff --git a/apps/sim/lib/copilot/chat/lifecycle.ts b/apps/sim/lib/copilot/chat/lifecycle.ts index 32ce1f12756..b1a53c31982 100644 --- a/apps/sim/lib/copilot/chat/lifecycle.ts +++ b/apps/sim/lib/copilot/chat/lifecycle.ts @@ -7,7 +7,10 @@ import { getActiveWorkflowRecord, } from '@sim/platform-authz/workflow' import { and, asc, eq, isNull, sql } from 'drizzle-orm' -import { authorizeOrganizationChat } from '@/lib/copilot/chat/organization-chats' +import { + authorizeOrganizationChat, + authorizeOrganizationChatCancellation, +} from '@/lib/copilot/chat/organization-chats' import { type PersistedMessage, stripToolResultOutput } from '@/lib/copilot/chat/persisted-message' import { asOrchestrationError } from '@/lib/core/orchestration/types' import { @@ -132,7 +135,10 @@ async function authorizeCopilotChatRow( chat: T | undefined, chatId: string, userId: string, - principal?: Principal + principal?: Principal, + organizationAuthorization: + | typeof authorizeOrganizationChat + | typeof authorizeOrganizationChatCancellation = authorizeOrganizationChat ): Promise { if (!chat) { logger.warn('Copilot chat not found or not owned by user', { chatId, userId }) @@ -141,8 +147,13 @@ async function authorizeCopilotChatRow( if (chat.organizationId) { if (!principal || resolvePrincipalSubjectUserId(principal) !== userId) return null + if ( + principal.kind === 'organization_delegated' && + (principal.serviceId !== 'copilot' || principal.resourceScope.chatId !== chat.id) + ) + return null try { - await authorizeOrganizationChat.execute({ + await organizationAuthorization.execute({ principal, input: { organizationId: chat.organizationId }, }) @@ -186,10 +197,40 @@ async function authorizeCopilotChatRow( * authorization check — use this for routes that only need ownership * verification before a mutation (rename, delete, update-messages). */ -export async function getAccessibleCopilotChatAuth( +export function getAccessibleCopilotChatAuth( chatId: string, userId: string, options?: { principal?: Principal } +): Promise { + return loadAccessibleCopilotChatAuth( + chatId, + userId, + options?.principal, + authorizeOrganizationChat + ) +} + +/** Resolves the same owned, live chat under the Stop operation's current membership policy. */ +export function getAccessibleCopilotChatForCancellation( + chatId: string, + userId: string, + options?: { principal?: Principal } +): Promise { + return loadAccessibleCopilotChatAuth( + chatId, + userId, + options?.principal, + authorizeOrganizationChatCancellation + ) +} + +async function loadAccessibleCopilotChatAuth( + chatId: string, + userId: string, + principal: Principal | undefined, + organizationAuthorization: + | typeof authorizeOrganizationChat + | typeof authorizeOrganizationChatCancellation ): Promise { const [chat] = await db .select(copilotChatAuthColumns) @@ -197,7 +238,7 @@ export async function getAccessibleCopilotChatAuth( .where(ownedLiveChatWhere(chatId, userId)) .limit(1) - return authorizeCopilotChatRow(chat, chatId, userId, options?.principal) + return authorizeCopilotChatRow(chat, chatId, userId, principal, organizationAuthorization) } /** diff --git a/apps/sim/lib/copilot/chat/organization-chats.test.ts b/apps/sim/lib/copilot/chat/organization-chats.test.ts index f5540d89d0e..a0489ea0e38 100644 --- a/apps/sim/lib/copilot/chat/organization-chats.test.ts +++ b/apps/sim/lib/copilot/chat/organization-chats.test.ts @@ -3,6 +3,7 @@ import { dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createTrustedOrganizationCopilotPrincipal } from '@/lib/copilot/auth/application-delegation' import { + authorizeOrganizationChatCancellation, authorizeOrganizationChatDelegation, authorizeOrganizationChatEvents, createOrganizationChat, @@ -67,6 +68,28 @@ describe('private organization chat delegation', () => { expect(dbChainMockFns.select).not.toHaveBeenCalled() }) + it('keeps cancellation member/chat checks while exempting the disabled Copilot capability', async () => { + dbChainMockFns.limit.mockResolvedValueOnce([{ id: 'private-chat' }]) + await authorizeOrganizationChatDelegation.execute({ + principal: { ...principal(), audience: 'sim:copilot-cancel' }, + }) + expect(authorize).toHaveBeenCalledWith( + expect.objectContaining({ subjectUserId: 'member-1' }), + expect.objectContaining({ + id: 'organization.chats.cancel', + minimumRole: 'member', + capability: 'none', + }), + { organizationId: 'org-1' } + ) + dbChainMockFns.limit.mockResolvedValueOnce([]) + await expect( + authorizeOrganizationChatDelegation.execute({ + principal: { ...principal(), audience: 'sim:copilot-cancel' }, + }) + ).rejects.toThrow('Conversation not found') + }) + it('does not accept an audience outside its registered operations', async () => { await expect( authorizeOrganizationChatDelegation.execute({ @@ -104,6 +127,24 @@ describe('organization chat events application boundary', () => { ) }) + it('uses the same cancellation policy for authenticated session and delegated callbacks', async () => { + await authorizeOrganizationChatCancellation.execute({ + principal, + input: { organizationId: 'org-1' }, + }) + expect(authorize).toHaveBeenCalledWith( + principal, + expect.objectContaining({ + id: 'organization.chats.cancel', + minimumRole: 'member', + capability: 'none', + principalKinds: ['session', 'organization_delegated'], + }), + { organizationId: 'org-1' } + ) + expect(requireSearch).not.toHaveBeenCalled() + }) + it('does not examine rollout state for a non-member', async () => { authorize.mockRejectedValueOnce(new OrchestrationError('not_found', 'Organization not found')) await expect( diff --git a/apps/sim/lib/copilot/chat/organization-chats.ts b/apps/sim/lib/copilot/chat/organization-chats.ts index 242f3ab68c8..c91833e8ffa 100644 --- a/apps/sim/lib/copilot/chat/organization-chats.ts +++ b/apps/sim/lib/copilot/chat/organization-chats.ts @@ -111,6 +111,17 @@ export const createOrganizationChat = { } export const organizationChatDelegationOperations = { + /** + * permission-group-exempt: Stopping existing work remains available after Copilot is disabled. + */ + cancel: defineOrganizationOperation({ + id: 'organization.chats.cancel', + minimumRole: 'member', + principalKinds: ['session', 'organization_delegated'], + capability: 'none', + delegationAudience: 'sim:copilot-cancel', + delegatedServices: ['copilot'], + }), knowledge: defineOrganizationOperation({ id: 'organization.chats.knowledge', minimumRole: 'member', @@ -129,6 +140,18 @@ export const organizationChatDelegationOperations = { }), } as const +/** Checks current membership for stopping an owned chat without requiring Copilot to remain enabled. */ +export const authorizeOrganizationChatCancellation = { + operation: organizationChatDelegationOperations.cancel, + execute({ principal, input }: { principal: Principal; input: OrganizationChatInput }) { + return authorizeOrganizationOperation( + principal, + organizationChatDelegationOperations.cancel, + input + ) + }, +} + /** A trusted service may act only on the subject's persisted private organization chat. */ export const authorizeOrganizationChatDelegation = { async execute({ principal }: { principal: OrganizationDelegatedPrincipal }) { diff --git a/apps/sim/lib/copilot/generated/billing-protocol-v1.ts b/apps/sim/lib/copilot/generated/billing-protocol-v1.ts index 482442a214b..493785411f1 100644 --- a/apps/sim/lib/copilot/generated/billing-protocol-v1.ts +++ b/apps/sim/lib/copilot/generated/billing-protocol-v1.ts @@ -32,6 +32,20 @@ export const COPILOT_BILLING_PROTOCOL_VALUES = [ COPILOT_BILLING_PROTOCOL.legacy, ] as const +export const COPILOT_VALIDATION_PURPOSE = { + newTurn: 'new-turn', + continuation: 'continuation', + cancellation: 'cancellation', +} as const + +export const COPILOT_VALIDATION_PURPOSE_VALUES = [ + 'new-turn', + 'continuation', + 'cancellation', +] as const + +export type CopilotValidationPurpose = (typeof COPILOT_VALIDATION_PURPOSE_VALUES)[number] + export const BILLING_ATTRIBUTION_HEADER_MAX_BYTES = 8192 export const BILLING_ACCOUNT_DECISION_HEADER_MAX_BYTES = 2048 diff --git a/apps/sim/lib/copilot/request/lifecycle/run.ts b/apps/sim/lib/copilot/request/lifecycle/run.ts index ba50ca1ef5e..265f7a8eb1b 100644 --- a/apps/sim/lib/copilot/request/lifecycle/run.ts +++ b/apps/sim/lib/copilot/request/lifecycle/run.ts @@ -990,11 +990,11 @@ async function runCheckpointLoop( payload = { ...payload, systemPromptOverride } } - // Go's auth middleware re-validates every Sim -> Go request by reading - // workspaceId from the JSON body and forwarding it to Sim's validate route, - // where it is required for the per-member usage gate. Normalize the initial - // leg from the lifecycle option so callers that only set the option (not the - // raw payload) still send it on the first request. + /** + * The initial turn needs its workspace for pooled and member spend admission. + * Resumes authenticate again, then Go rechecks current access using the + * checkpoint's original scope and payer without repeating spend admission. + */ if (lifecycleWorkspaceId && !nonBlankString(payload.workspaceId)) { payload = { ...payload, workspaceId: lifecycleWorkspaceId } } diff --git a/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts b/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts index 5d03eab792b..899982f5b78 100644 --- a/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts @@ -9,6 +9,8 @@ import { embedding, knowledgeBase, knowledgeConnector, + knowledgeConnectorMember, + knowledgeDocumentObservation, member, organization, user, @@ -35,6 +37,7 @@ import { seedSearchReaderFixture } from '@/lib/knowledge/__integration__/seed-se import { createKnowledgeAclFixtureIds, seedKnowledgeAclFixture, + seedKnowledgeMemberFixture, } from '@/lib/knowledge/__integration__/seed-source-access-fixture' import { SearchBudget, @@ -127,6 +130,7 @@ interface CapturedQuery { interface ExplainNode { 'Node Type': string 'Actual Rows': number + 'Actual Loops': number 'Index Name'?: string 'Relation Name'?: string Output?: string[] @@ -138,6 +142,7 @@ const explainNodeSchema: z.ZodType = z.lazy(() => .object({ 'Node Type': z.string(), 'Actual Rows': z.number(), + 'Actual Loops': z.number(), 'Index Name': z.string().optional(), 'Relation Name': z.string().optional(), Output: z.array(z.string()).optional(), @@ -155,6 +160,19 @@ function assertCompactCandidates(node: ExplainNode) { for (const child of node.Plans ?? []) assertCompactCandidates(child) } +/** Small scopes must seek chunk metadata by document without reading the full vector projection. */ +function assertIndexedChunkProbe(node: ExplainNode): number { + let lookups = 0 + if (node['Relation Name'] === 'embedding_search') { + expect(['Index Scan', 'Index Only Scan']).toContain(node['Node Type']) + expect(node['Index Name']).toBe('embedding_search_document_lookup_idx') + expect((node.Output ?? []).join(' ')).not.toMatch(/(?:embedding_search\.)?(?:vector|binary)/) + lookups = node['Actual Loops'] + } + for (const child of node.Plans ?? []) lookups += assertIndexedChunkProbe(child) + return lookups +} + /** Keyword sort memory must scale with identities and scores, not the matched document text. */ function assertScalarKeywordSorts(node: ExplainNode) { if (node['Node Type'] === 'Sort') { @@ -227,10 +245,10 @@ async function search( ) } -async function searchDashboard(query = 'Orion deployment') { +async function searchDashboard(query = 'Orion deployment', userId = ids.aliceId) { const authenticate = vi.spyOn(internalSessionAuth, 'authenticate').mockResolvedValue({ kind: 'session', - userId: ids.aliceId, + userId, sessionId: 'fixture-dashboard', }) try { @@ -305,6 +323,7 @@ async function sample(label: string, run: () => ReturnType) { item.query.includes('FROM "embedding_keyword_search"')) && (item.query.includes('order by') || item.query.includes('limit') || + item.query.includes('CROSS JOIN LATERAL') || item.query.includes('WITH visible_search_documents') || item.query.includes('WITH scored_search_candidates') || item.query.includes('WITH visible_keyword_documents')) @@ -313,6 +332,7 @@ async function sample(label: string, run: () => ReturnType) { for (const query of searches) { const plan = await db.$client.begin(async (tx) => { await tx.unsafe("SET LOCAL statement_timeout = '45s'") + await tx.unsafe('SET LOCAL jit = off') await tx.unsafe("SET LOCAL hnsw.iterative_scan = 'relaxed_order'") await tx.unsafe('SET LOCAL hnsw.max_scan_tuples = 20000') if ( @@ -565,6 +585,19 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu expect(settings.timeout).toBe('0') }) + it('disables compilation only inside deadline-bound search transactions', async () => { + const [before] = await db.execute<{ jit: string }>(sql`SELECT current_setting('jit') AS jit`) + for (const leg of ['vector', 'keyword', 'tags'] as const) { + const budget = new SearchBudget(leg, performance.now() + 2000) + const [inside] = await budget.query(`${leg}.sql`, (executor) => + executor.execute<{ jit: string }>(sql`SELECT current_setting('jit') AS jit`) + ) + expect(inside.jit).toBe('off') + } + const [settings] = await db.execute<{ jit: string }>(sql`SELECT current_setting('jit') AS jit`) + expect(settings.jit).toBe(before.jit) + }) + it('expires waiting for a saturated pool without executing abandoned work', async () => { let release!: () => void const released = new Promise((resolve) => { @@ -834,22 +867,31 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu }, 180_000) it('ranks a small permission scope by its bounded IDs without a corpus-wide vector probe', async () => { - const documentIds = [0, 8, 16].map((index) => `${ids.workspaceId}-doc-${index}`) + const lastTopicDocument = Math.floor((chunkCount / chunksPerDocument - 1) / 8) * 8 + const documentIds = [0, 8, 16].map( + (offset) => `${ids.workspaceId}-doc-${lastTopicDocument - offset}` + ) await db .update(document) .set({ acl: [`u:${ids.aliceId}@fixture.test`, `u:${ids.bobId}@fixture.test`] }) .where(inArray(document.id, documentIds)) try { - const { result, plans } = await sample('small-scope', () => search(ids.bobId)) - expect(result.data.results.length).toBeGreaterThan(0) - expect(result.data.results.every((row) => documentIds.includes(row.documentId))).toBe(true) - const probe = plans.filter((plan) => plan.kind === 'probe') - expect(probe).toHaveLength(1) - expect(probe[0].query).not.toContain('<=>') - expect(probe[0].plan[0].Plan['Actual Rows']).toBe(12) - const vector = plans.filter((plan) => plan.kind === 'rerank') - expect(vector).toHaveLength(1) - expect(vector[0].query).toContain('"embedding"."id" in') + for (const surface of ['copilot', 'dashboard'] as const) { + const { result, plans, diagnostics } = await sample(`small-scope.${surface}`, () => + surface === 'copilot' ? search(ids.bobId) : searchDashboard('Orion deployment', ids.bobId) + ) + expectCompleteVectorSearch(diagnostics) + expect(result.data.results.length).toBeGreaterThan(0) + expect(result.data.results.every((row) => documentIds.includes(row.documentId))).toBe(true) + const probe = plans.filter((plan) => plan.kind === 'probe') + expect(probe).toHaveLength(1) + expect(probe[0].query).not.toContain('<=>') + expect(probe[0].plan[0].Plan['Actual Rows']).toBe(12) + expect(assertIndexedChunkProbe(probe[0].plan[0].Plan)).toBe(documentIds.length) + const vector = plans.filter((plan) => plan.kind === 'rerank') + expect(vector).toHaveLength(1) + expect(vector[0].query).toContain('"embedding"."id" in') + } } finally { await db .update(document) @@ -858,6 +900,120 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu } }, 180_000) + it.each([200, 396, 400])( + 'keeps a selective scope of %s chunks within both retrieval budgets', + async (count) => { + const documentCount = count / chunksPerDocument + const documentIds = Array.from( + { length: documentCount }, + (_, index) => `${ids.workspaceId}-doc-${chunkCount / chunksPerDocument - 1 - index}` + ) + await db + .update(document) + .set({ acl: [`u:${ids.aliceId}@fixture.test`, `u:${ids.bobId}@fixture.test`] }) + .where(inArray(document.id, documentIds)) + try { + for (const surface of ['copilot', 'dashboard'] as const) { + const { result, plans, diagnostics } = await sample( + `selective-${count}.${surface}`, + () => + surface === 'copilot' + ? search(ids.bobId) + : searchDashboard('Orion deployment', ids.bobId) + ) + expectCompleteVectorSearch(diagnostics) + expect(result.data.results.length).toBeGreaterThan(0) + expect(result.data.results.every((row) => documentIds.includes(row.documentId))).toBe( + true + ) + const probe = plans.find((plan) => plan.kind === 'probe')! + expect(probe.plan[0].Plan['Actual Rows']).toBe(count) + expect(assertIndexedChunkProbe(probe.plan[0].Plan)).toBe(documentCount) + expect(plans.filter((plan) => plan.kind === 'vector')).toHaveLength(count < 400 ? 0 : 1) + } + } finally { + await db + .update(document) + .set({ acl: [`u:${ids.aliceId}@fixture.test`] }) + .where(inArray(document.id, documentIds)) + } + }, + 180_000 + ) + + it('bounds member-observation searches and rejects suspended readers with current ACL checks', async () => { + const fixture = await seedKnowledgeMemberFixture(ids) + const [alice, bob] = fixture.members + const lastTopicDocument = Math.floor((chunkCount / chunksPerDocument - 1) / 8) * 8 + const documentIds = [0, 8, 16].map( + (offset) => `${ids.workspaceId}-doc-${lastTopicDocument - offset}` + ) + try { + await db + .update(document) + .set({ connectorId: fixture.connectorId, acl: [alice.subjectToken] }) + .where(eq(document.knowledgeBaseId, ids.knowledgeBaseId)) + await db.execute(sql`INSERT INTO knowledge_document_observation + (document_id, member_id, run_id) + SELECT id, ${alice.id}, ${fixture.runId} FROM document + WHERE knowledge_base_id = ${ids.knowledgeBaseId}`) + await db.insert(knowledgeDocumentObservation).values( + documentIds.map((documentId) => ({ + documentId, + memberId: bob.id, + runId: fixture.runId, + })) + ) + await db + .update(document) + .set({ acl: [alice.subjectToken, bob.subjectToken] }) + .where(inArray(document.id, documentIds)) + await db.execute(sql`ANALYZE document`) + await db.execute(sql`ANALYZE knowledge_document_observation`) + for (const surface of ['copilot', 'dashboard'] as const) { + const broad = await sample(`member-broad.${surface}`, () => + surface === 'copilot' ? search() : searchDashboard() + ) + expectCompleteVectorSearch(broad.diagnostics) + expect(broad.result.data.results).toHaveLength(15) + const broadProbe = broad.plans.find((plan) => plan.kind === 'probe')! + expect(broadProbe.plan[0].Plan['Actual Rows']).toBe(400) + expect(assertIndexedChunkProbe(broadProbe.plan[0].Plan)).toBe(400 / chunksPerDocument) + const { result, plans, diagnostics } = await sample(`member-scope.${surface}`, () => + surface === 'copilot' ? search(ids.bobId) : searchDashboard('Orion deployment', ids.bobId) + ) + expectCompleteVectorSearch(diagnostics) + expect(result.data.results.length).toBeGreaterThan(0) + expect(result.data.results.every((row) => documentIds.includes(row.documentId))).toBe(true) + const probe = plans.find((plan) => plan.kind === 'probe')! + expect(probe).toBeDefined() + expect(probe.query).toContain('knowledge_document_observation') + expect(assertIndexedChunkProbe(probe.plan[0].Plan)).toBe(documentIds.length) + } + await db + .update(knowledgeConnectorMember) + .set({ status: 'suspended' }) + .where(eq(knowledgeConnectorMember.id, bob.id)) + const denied = await sample('member-scope.suspended', () => search(ids.bobId)) + expectCompleteVectorSearch(denied.diagnostics) + expect(denied.result.data.results).toEqual([]) + } finally { + await db + .update(document) + .set({ connectorId: ids.connectorId, acl: [`u:${ids.aliceId}@fixture.test`] }) + .where(eq(document.knowledgeBaseId, ids.knowledgeBaseId)) + await db.delete(knowledgeConnector).where(eq(knowledgeConnector.id, fixture.connectorId)) + await db.delete(credential).where( + inArray( + credential.id, + fixture.members.map((member) => member.credentialId) + ) + ) + await db.delete(credentialGroup).where(eq(credentialGroup.id, fixture.groupId)) + await db.execute(sql`ANALYZE document`) + } + }, 180_000) + it('applies selective document scope and exclusion before ranking', async () => { const documentIds = [0, 8, 16, 24, 32].map((index) => `${ids.workspaceId}-doc-${index}`) await db.update(document).set({ userExcluded: true }).where(eq(document.id, documentIds[0])) diff --git a/apps/sim/lib/knowledge/search/budget.ts b/apps/sim/lib/knowledge/search/budget.ts index 8011733d926..a80279ec3e1 100644 --- a/apps/sim/lib/knowledge/search/budget.ts +++ b/apps/sim/lib/knowledge/search/budget.ts @@ -85,7 +85,10 @@ export class SearchBudget { if (expired) throw new SearchDeadlineError() recordSearchStageDuration(`${this.leg}.connection_acquire`, performance.now() - started) const timeout = String(this.remaining()) - await tx.execute(sql`SELECT set_config('statement_timeout', ${timeout}, true)`) + /** Interactive retrieval cannot amortize compilation of the access predicates. */ + await tx.execute( + sql`SELECT set_config('statement_timeout', ${timeout}, true), set_config('jit', 'off', true)` + ) this.remaining() return measureSearchStage(stage, () => run(tx)) }) diff --git a/apps/sim/lib/knowledge/search/queries.test.ts b/apps/sim/lib/knowledge/search/queries.test.ts index 0dda4659143..1a6170a0a33 100644 --- a/apps/sim/lib/knowledge/search/queries.test.ts +++ b/apps/sim/lib/knowledge/search/queries.test.ts @@ -573,6 +573,7 @@ describe('live repository authorization follows ranked candidates', () => { structuredFilters: [{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'release' }], } + const probePages: Array> = [] const candidatePages: Array> = [] const rerankPages: Array>> = [] const keywordPages: Array>> = [] @@ -585,17 +586,20 @@ describe('live repository authorization follows ranked candidates', () => { beforeEach(() => { resetDbChainMock() + probePages.length = 0 candidatePages.length = 0 rerankPages.length = 0 keywordPages.length = 0 dbChainMockFns.execute.mockImplementation(async (query) => - render(query).sql.includes('WITH visible_search_documents') - ? (candidatePages.shift() ?? []) - : render(query).sql.includes('WITH scored_search_candidates') - ? (rerankPages.shift() ?? []) - : render(query).sql.includes('WITH visible_keyword_documents') - ? (keywordPages.shift() ?? []) - : [] + render(query).sql.includes('CROSS JOIN LATERAL') + ? (probePages.shift() ?? []) + : render(query).sql.includes('WITH visible_search_documents') + ? (candidatePages.shift() ?? []) + : render(query).sql.includes('WITH scored_search_candidates') + ? (rerankPages.shift() ?? []) + : render(query).sql.includes('WITH visible_keyword_documents') + ? (keywordPages.shift() ?? []) + : [] ) getForConnectors.mockReset().mockResolvedValue(allowed) }) @@ -603,9 +607,8 @@ describe('live repository authorization follows ranked candidates', () => { afterEach(() => vi.useRealTimers()) it('bounds broad vector ranking before metadata and reorders relaxed candidates before trimming', async () => { - queueTableRows( - schemaMock.embeddingSearch, - Array.from({ length: 200 }, (_, index) => candidate(`probe-${index}`, 'allowed-source')) + probePages.push( + Array.from({ length: 400 }, (_, index) => candidate(`probe-${index}`, 'allowed-source')) ) queueCandidates(Array.from({ length: 400 }, (_, index) => ({ id: `candidate-${index}` }))) queueRerank([ @@ -640,16 +643,18 @@ describe('live repository authorization follows ranked candidates', () => { }) it('finishes empty scopes after the bounded probe without scanning HNSW or calling providers', async () => { - queueTableRows(schemaMock.embeddingSearch, []) + probePages.push([]) expect(await handleVectorOnlySearch({ ...params, structuredFilters: undefined })).toEqual([]) - expect(dbChainMockFns.select).toHaveBeenCalledOnce() - expect(dbChainMockFns.limit).toHaveBeenCalledExactlyOnceWith(200) + expect(dbChainMockFns.select).not.toHaveBeenCalled() + const probe = render(dbChainMockFns.execute.mock.calls[0][0]) + expect(probe.sql).toContain('CROSS JOIN LATERAL') + expect(probe.params.filter((value) => value === 400)).toHaveLength(2) expect(dbChainMockFns.orderBy).not.toHaveBeenCalled() expect(getForConnectors).not.toHaveBeenCalled() }) it('reads vectors only for the bounded IDs when a broad scope has few candidates', async () => { - queueTableRows(schemaMock.embeddingSearch, [candidate('selected', 'allowed-source')]) + probePages.push([candidate('selected', 'allowed-source')]) queueTableRows(schemaMock.embedding, [candidate('selected', 'allowed-source')]) queueTableRows(schemaMock.embedding, [ { id: 'selected', content: 'Verified small scope', distance: 0.1 }, @@ -657,11 +662,13 @@ describe('live repository authorization follows ranked candidates', () => { expect(await handleVectorOnlySearch({ ...params, structuredFilters: undefined })).toEqual([ { id: 'selected', content: 'Verified small scope', distance: 0.1 }, ]) - expect(Object.keys(dbChainMockFns.select.mock.calls[0][0])).toEqual(['id']) - expect(JSON.stringify(dbChainMockFns.where.mock.calls[0][0])).not.toContain('<=>') + const probe = dbChainMockFns.execute.mock.calls[0][0] + expect(render(probe).sql).toContain('SELECT scoped_chunk.id') + expect(render(probe).sql).not.toContain('<=>') + expect(JSON.stringify(probe)).toContain('required_clause') expect( hasMockCondition( - dbChainMockFns.where.mock.calls[1][0], + dbChainMockFns.where.mock.calls[0][0], (node) => node.type === 'inArray' && node.column === schemaMock.embedding.id && @@ -673,11 +680,34 @@ describe('live repository authorization follows ranked candidates', () => { expect(getForConnectors).toHaveBeenCalledExactlyOnceWith(['allowed-source'], undefined) }) + it.each([199, 200, 399])( + 'ranks an exhausted scope of %s chunks once without repeating candidate search', + async (count) => { + const probe = Array.from({ length: count }, (_, index) => ({ id: `chunk-${index}` })) + probePages.push(probe) + queueTableRows(schemaMock.embedding, [candidate('chunk-0', 'allowed-source')]) + queueTableRows(schemaMock.embedding, [ + { id: 'chunk-0', content: 'Authorized passage', distance: 0.1 }, + ]) + const rows = await handleVectorOnlySearch({ ...params, structuredFilters: undefined }) + expect(rows.map((row) => row.id)).toEqual(['chunk-0']) + expect(dbChainMockFns.execute).toHaveBeenCalledOnce() + expect( + hasMockCondition( + dbChainMockFns.where.mock.calls[0][0], + (node) => + node.type === 'inArray' && + node.column === schemaMock.embedding.id && + Array.isArray(node.values) && + node.values.length === count + ) + ).toBe(true) + expect(dbChainMockFns.orderBy).toHaveBeenCalledOnce() + } + ) + it('scans the filtered projection when ANN cannot fill its limit', async () => { - queueTableRows( - schemaMock.embeddingSearch, - Array.from({ length: 200 }, (_, index) => ({ id: `probe-${index}` })) - ) + probePages.push(Array.from({ length: 400 }, (_, index) => ({ id: `probe-${index}` }))) queueCandidates([{ id: 'selected' }], 1) queueRerank([candidate('selected', 'allowed-source')]) queueTableRows(schemaMock.embedding, [ @@ -697,15 +727,15 @@ describe('live repository authorization follows ranked candidates', () => { }) it('advances past candidate pages that hydrate no current readable content', async () => { - const probe = Array.from({ length: 200 }, (_, index) => ({ id: `probe-${index}` })) + const probe = Array.from({ length: 400 }, (_, index) => ({ id: `probe-${index}` })) const identities = Array.from({ length: 400 }, (_, index) => ({ id: `candidate-${index}` })) - queueTableRows(schemaMock.embeddingSearch, probe) + probePages.push(probe) queueCandidates(identities) queueRerank( Array.from({ length: 20 }, (_, index) => candidate(`candidate-${index}`, 'allowed-source')) ) queueTableRows(schemaMock.embedding, []) - queueTableRows(schemaMock.embeddingSearch, probe) + probePages.push(probe) queueCandidates(identities) queueRerank([candidate('selected', 'allowed-source')]) queueTableRows(schemaMock.embedding, [ @@ -722,17 +752,17 @@ describe('live repository authorization follows ranked candidates', () => { }) it('sorts hydrated candidates across pages by their original-vector distance', async () => { - const probe = Array.from({ length: 200 }, (_, index) => + const probe = Array.from({ length: 400 }, (_, index) => candidate(`probe-${index}`, 'allowed-source') ) - queueTableRows(schemaMock.embeddingSearch, probe) + probePages.push(probe) queueCandidates(Array.from({ length: 400 }, (_, index) => ({ id: `candidate-${index}` }))) queueRerank([ { ...candidate('far', 'allowed-source'), distance: 0.7 }, ...Array.from({ length: 19 }, (_, index) => candidate(`hidden-${index}`, 'allowed-source')), ]) queueTableRows(schemaMock.embedding, [{ id: 'far', content: 'Far result', distance: 0.7 }]) - queueTableRows(schemaMock.embeddingSearch, probe) + probePages.push(probe) queueCandidates(Array.from({ length: 400 }, (_, index) => ({ id: `candidate-${index}` }))) queueRerank([ candidate('near', 'allowed-source'), diff --git a/apps/sim/lib/knowledge/search/queries.ts b/apps/sim/lib/knowledge/search/queries.ts index bf541a2fd08..46fe5069783 100644 --- a/apps/sim/lib/knowledge/search/queries.ts +++ b/apps/sim/lib/knowledge/search/queries.ts @@ -860,10 +860,6 @@ async function selectLiveVectorResults( ), excludeSearchSources(excludedSources), ] - const candidateVisibility = [ - eq(embeddingSearch.enabled, true), - ...candidateDocumentVisibility, - ] /** Explicitly filtered scopes use exact ordering instead of HNSW traversal. */ const exactPage = async (candidateIds?: string[]) => { annotateSearchDiagnostics({ vectorRanking: 'exact' }) @@ -888,22 +884,29 @@ async function selectLiveVectorResults( if (params.filters?.documentIds?.length || params.structuredFilters?.length) { return exactPage() } - /** Probe visibility without vector reads; revoked scopes must not detoast the corpus. */ + /** + * Enumerate bounded chunk identities from visible documents. The lateral limit keeps + * the probe on document-indexed lookups instead of hashing the entire vector projection. + * An exhausted probe fits in the rerank pool and needs only one exact ranking pass. + */ const probe = await runSearchQuery(params.budget, 'vector.probe', (executor) => - executor - .select({ id: embeddingSearch.id }) - .from(embeddingSearch) - .innerJoin(document, eq(document.id, embeddingSearch.documentId)) - .where( - and( + executor.execute<{ id: string }>(sql` + SELECT scoped_chunk.id FROM ${document} + CROSS JOIN LATERAL ( + SELECT ${embeddingSearch.id} AS id FROM ${embeddingSearch} + WHERE ${and( + eq(embeddingSearch.documentId, document.id), inArray(embeddingSearch.knowledgeBaseId, params.knowledgeBaseIds), - ...candidateVisibility - ) - ) - .limit(LIVE_SEARCH_PAGE_SIZE) + eq(embeddingSearch.enabled, true) + )} + LIMIT ${candidateLimit} + ) AS scoped_chunk + WHERE ${and(...candidateDocumentVisibility)} + LIMIT ${candidateLimit} + `) ) if (probe.length === 0) return { candidates: [], nextOffset: offset } - if (probe.length < LIVE_SEARCH_PAGE_SIZE) { + if (probe.length < candidateLimit) { return exactPage(probe.map((candidate) => candidate.id)) } annotateSearchDiagnostics({ diff --git a/packages/db/migrations/0352_search_document_lookup.sql b/packages/db/migrations/0352_search_document_lookup.sql new file mode 100644 index 00000000000..494101e334b --- /dev/null +++ b/packages/db/migrations/0352_search_document_lookup.sql @@ -0,0 +1,2 @@ +-- Script migration 0017 builds embedding_search_document_lookup_idx concurrently. +-- The shared search-index builder repairs interrupted builds and preserves valid indexes on replay. diff --git a/packages/db/migrations/meta/0352_snapshot.json b/packages/db/migrations/meta/0352_snapshot.json new file mode 100644 index 00000000000..23673246266 --- /dev/null +++ b/packages/db/migrations/meta/0352_snapshot.json @@ -0,0 +1,27079 @@ +{ + "id": "77bb7f33-ce24-4df1-a756-94984877c940", + "prevId": "68c2e94c-a38f-4cb3-aded-181cd867260f", + "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.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 + }, + "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_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_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" + } + }, + "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.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_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_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_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_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": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "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_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_vector_hnsw_idx": { + "name": "embedding_384_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_vector_hnsw_idx": { + "name": "embedding_768_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_vector_hnsw_idx": { + "name": "embedding_1024_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_vector_hnsw_idx": { + "name": "embedding_3072_vector_hnsw_idx", + "columns": [ + { + "expression": "(\"embedding_3072\"::halfvec(3072)) halfvec_cosine_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_binary_hnsw_idx": { + "name": "embedding_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding\")::bit(1536)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_binary_hnsw_idx": { + "name": "embedding_384_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_384\")::bit(384)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_binary_hnsw_idx": { + "name": "embedding_768_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_768\")::bit(768)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_binary_hnsw_idx": { + "name": "embedding_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_1024\")::bit(1024)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_binary_hnsw_idx": { + "name": "embedding_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_3072\")::bit(3072)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "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": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_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_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 + }, + "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_binary_hnsw_idx": { + "name": "embedding_search_binary_hnsw_idx", + "columns": [ + { + "expression": "binary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_384_binary_hnsw_idx": { + "name": "embedding_search_384_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_768_binary_hnsw_idx": { + "name": "embedding_search_768_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_1024_binary_hnsw_idx": { + "name": "embedding_search_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_3072_binary_hnsw_idx": { + "name": "embedding_search_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "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", + "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 + }, + "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", + "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 + } + }, + "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 + }, + "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_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_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 + } + }, + "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')" + } + }, + "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 + } + }, + "indexes": { + "kcsl_connector_started_at_idx": { + "name": "kcsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcsl_started_at_partial_idx": { + "name": "kcsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector_sync_log\".\"status\" = 'started'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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.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 + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_secret_provenance": { + "name": "memory_secret_provenance", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memory_secret_provenance_memory_id_memory_id_fk": { + "name": "memory_secret_provenance_memory_id_memory_id_fk", + "tableFrom": "memory_secret_provenance", + "tableTo": "memory", + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_secret_provenance_status_check": { + "name": "memory_secret_provenance_status_check", + "value": "\"memory_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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_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.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.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_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_period_cost_idx": { + "name": "usage_log_billing_period_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_created_at_cost_idx": { + "name": "usage_log_billing_entity_created_at_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_deadline_idx": { + "name": "workflow_execution_logs_running_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_started_at_idx": { + "name": "workflow_execution_logs_redacting_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'redacting'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_deadline_idx": { + "name": "workflow_execution_logs_redacting_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "mounted_secrets": { + "name": "mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_operation", + "columnsFrom": ["deployment_operation_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_secret_scope": { + "name": "inbox_secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "inbox_mounted_secrets": { + "name": "inbox_mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_inbox_provider_id_idx": { + "name": "workspace_inbox_provider_id_idx", + "columns": [ + { + "expression": "inbox_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_storage_used_bytes_non_negative": { + "name": "workspace_storage_used_bytes_non_negative", + "value": "\"workspace\".\"storage_used_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_collab_state": { + "name": "workspace_file_collab_state", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "doc_state": { + "name": "doc_state", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_collab_state_file_id_workspace_files_id_fk": { + "name": "workspace_file_collab_state_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_collab_state", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_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_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_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_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": "now()" + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_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 + } + }, + "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_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 2444c425a11..80a76668d72 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2458,6 +2458,13 @@ "when": 1789583796385, "tag": "0351_copilot_run_activity_index", "breakpoints": true + }, + { + "idx": 352, + "version": "7", + "when": 1789588587193, + "tag": "0352_search_document_lookup", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 833473fff8b..f80a377dc49 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -3343,6 +3343,10 @@ export const embeddingSearch = pgTable( }, (table) => ({ knowledgeBaseIdx: index('embedding_search_kb_idx').on(table.knowledgeBaseId), + documentLookupIdx: index('embedding_search_document_lookup_idx') + .on(table.documentId, table.knowledgeBaseId, table.id) + .concurrently() + .where(sql`${table.enabled}`), binaryIdx: index('embedding_search_binary_hnsw_idx') .using('hnsw', table.binary.op('bit_hamming_ops')) .with({ m: 16, ef_construction: 64 }), diff --git a/packages/db/script-migrations-paused-billing-attribution.test.ts b/packages/db/script-migrations-paused-billing-attribution.test.ts index 6bdc8525e50..3c75c7f1d8a 100644 --- a/packages/db/script-migrations-paused-billing-attribution.test.ts +++ b/packages/db/script-migrations-paused-billing-attribution.test.ts @@ -450,6 +450,7 @@ describe('script migration registry', () => { '0013_backfill_legacy_knowledge_base_workspaces', '0014_require_knowledge_base_owner', '0016_backfill_search_vectors', + '0017_index_search_documents', ]) }) }) diff --git a/packages/db/script-migrations/0016_backfill_search_vectors.postgres.test.ts b/packages/db/script-migrations/0016_backfill_search_vectors.postgres.test.ts index de22b5c5c29..e8f6265debb 100644 --- a/packages/db/script-migrations/0016_backfill_search_vectors.postgres.test.ts +++ b/packages/db/script-migrations/0016_backfill_search_vectors.postgres.test.ts @@ -169,20 +169,55 @@ describe.runIf(Boolean(databaseUrl))('search projection upgrade in PostgreSQL', INNER JOIN pg_class ON pg_class.oid = pg_index.indexrelid WHERE indrelid IN ('embedding_search'::regclass, 'embedding_keyword_search'::regclass) AND (relname LIKE '%cosine_hnsw_idx' OR relname IN - ('embedding_keyword_search_kb_idx', 'embedding_keyword_search_document_idx', 'embedding_keyword_search_content_idx')) + ('embedding_search_document_lookup_idx', 'embedding_keyword_search_kb_idx', 'embedding_keyword_search_document_idx', 'embedding_keyword_search_content_idx')) ORDER BY indexrelid` - expect(indexes).toHaveLength(9) + expect(indexes).toHaveLength(10) expect(indexes.every((index) => index.indisvalid)).toBe(true) await buildSearchIndexes(sql) const replay = await sql`SELECT indexrelid, indisvalid FROM pg_index INNER JOIN pg_class ON pg_class.oid = pg_index.indexrelid WHERE indrelid IN ('embedding_search'::regclass, 'embedding_keyword_search'::regclass) AND (relname LIKE '%cosine_hnsw_idx' OR relname IN - ('embedding_keyword_search_kb_idx', 'embedding_keyword_search_document_idx', 'embedding_keyword_search_content_idx')) + ('embedding_search_document_lookup_idx', 'embedding_keyword_search_kb_idx', 'embedding_keyword_search_document_idx', 'embedding_keyword_search_content_idx')) ORDER BY indexrelid` expect(replay).toEqual(indexes) }, 60_000) + it('allows other writers while the document lookup index waits for an existing writer', async () => { + await sql.unsafe('DROP INDEX embedding_search_document_lookup_idx') + const writer = postgres(databaseUrl!, { + max: 1, + connection: { search_path: `${schemaName},public` }, + }) + const [{ pid }] = await sql`SELECT pg_backend_pid() AS pid` + await writer`BEGIN` + await writer`LOCK TABLE embedding_search IN ROW EXCLUSIVE MODE` + const build = Promise.allSettled([buildSearchIndexes(sql)]) + try { + await vi.waitFor(async () => { + const [{ waiting }] = await admin`SELECT wait_event_type = 'Lock' AS waiting + FROM pg_stat_activity WHERE pid = ${pid}` + expect(waiting).toBe(true) + }) + await admin.begin(async (tx) => { + await tx.unsafe("SET LOCAL lock_timeout = '1s'") + await tx.unsafe(`LOCK TABLE "${schemaName}".embedding_search IN ROW EXCLUSIVE MODE`) + }) + } finally { + await writer`ROLLBACK` + await writer.end() + await build + } + const [result] = await build + if (result.status === 'rejected') throw result.reason + expect( + ( + await sql`SELECT indisvalid FROM pg_index + WHERE indexrelid = 'embedding_search_document_lookup_idx'::regclass` + )[0].indisvalid + ).toBe(true) + }) + it('keeps inserts, state changes, width changes, and deletes synchronous after the upgrade', async () => { await sql.unsafe(`INSERT INTO embedding (id, knowledge_base_id, document_id, chunk_index, chunk_hash, content, content_length, token_count, start_offset, end_offset, embedding_384) @@ -316,7 +351,7 @@ describe.runIf(Boolean(databaseUrl))('search projection upgrade in PostgreSQL', it('runs 0016 directly after a partially committed, unjournaled 0015', async () => { await sql`CREATE TABLE script_migrations (name text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())` for (const migration of scriptMigrations) { - if (migration.name !== '0016_backfill_search_vectors') { + if (migration.name < '0015') { await sql`INSERT INTO script_migrations (name) VALUES (${migration.name})` } } @@ -353,6 +388,7 @@ describe.runIf(Boolean(databaseUrl))('search projection upgrade in PostgreSQL', ).toEqual([ { name: '0015_backfill_embedding_search' }, { name: '0016_backfill_search_vectors' }, + { name: '0017_index_search_documents' }, ]) const [{ complete }] = await sql`SELECT count(*)::int AS complete FROM embedding e JOIN embedding_search s ON s.id = e.id JOIN embedding_keyword_search k ON k.id = e.id diff --git a/packages/db/script-migrations/0016_backfill_search_vectors.ts b/packages/db/script-migrations/0016_backfill_search_vectors.ts index 3b589cfbd82..e40be56c391 100644 --- a/packages/db/script-migrations/0016_backfill_search_vectors.ts +++ b/packages/db/script-migrations/0016_backfill_search_vectors.ts @@ -187,6 +187,11 @@ export async function backfillSearchKeywords(sql: Sql): Promise { /** Builds new indexes after bulk loading; interrupted builds are repaired without rebuilding valid ones. */ export async function buildSearchIndexes(sql: Sql): Promise { const indexes = [ + { + name: 'embedding_search_document_lookup_idx', + table: 'embedding_search', + definition: 'ON embedding_search (document_id, knowledge_base_id, id) WHERE enabled', + }, ...WIDTHS.map((width) => ({ name: `embedding_search${width === 1536 ? '' : `_${width}`}_cosine_hnsw_idx`, table: 'embedding_search', diff --git a/packages/db/script-migrations/0017_index_search_documents.ts b/packages/db/script-migrations/0017_index_search_documents.ts new file mode 100644 index 00000000000..43ca9993427 --- /dev/null +++ b/packages/db/script-migrations/0017_index_search_documents.ts @@ -0,0 +1,7 @@ +import { buildSearchIndexes } from '@sim/db/script-migrations/0016_backfill_search_vectors' +import type { ScriptMigration } from '@sim/db/script-migrations/types' + +export const indexSearchDocumentsMigration: ScriptMigration = { + name: '0017_index_search_documents', + up: buildSearchIndexes, +} diff --git a/packages/db/script-migrations/index.ts b/packages/db/script-migrations/index.ts index 9a439ac6de4..63f276fae8f 100644 --- a/packages/db/script-migrations/index.ts +++ b/packages/db/script-migrations/index.ts @@ -2,6 +2,7 @@ import { reconcileOAuthProviderLifecycleMigration } from '@sim/db/script-migrati import { backfillLegacyKnowledgeBaseWorkspacesMigration } from '@sim/db/script-migrations/0013_backfill_legacy_knowledge_base_workspaces' import { requireKnowledgeBaseOwnerMigration } from '@sim/db/script-migrations/0014_require_knowledge_base_owner' import { backfillSearchVectorsMigration } from '@sim/db/script-migrations/0016_backfill_search_vectors' +import { indexSearchDocumentsMigration } from '@sim/db/script-migrations/0017_index_search_documents' import type { Sql } from 'postgres' import { backfillTableOrderKeys } from './0001_backfill_table_order_keys' import { backfillPausedBillingAttribution } from './0002_backfill_paused_billing_attribution' @@ -36,6 +37,7 @@ export const scriptMigrations: readonly ScriptMigration[] = [ requireKnowledgeBaseOwnerMigration, /** 0016 completes partially applied 0015 binary projections together with the new search vectors. */ backfillSearchVectorsMigration, + indexSearchDocumentsMigration, ] /** diff --git a/scripts/sync-billing-protocol-contract.ts b/scripts/sync-billing-protocol-contract.ts index de681103b91..2303f258394 100644 --- a/scripts/sync-billing-protocol-contract.ts +++ b/scripts/sync-billing-protocol-contract.ts @@ -195,6 +195,10 @@ function render(schema: SchemaNode): string { 'BillingProtocolV1Limits.accountDecisionHeaderMaxBytes' ) const callbackOutcomes = namedPairs(definitions, 'BillingProtocolV1CallbackOutcomes') + const validationPurposes = stringEnum( + schemaDefinition(definitions, 'CopilotValidationPurpose'), + 'CopilotValidationPurpose' + ) const analyticsOutcomes = stringEnum( schemaDefinition(definitions, 'BillingAnalyticsOutcome'), 'BillingAnalyticsOutcome' @@ -238,6 +242,22 @@ export const COPILOT_BILLING_PROTOCOL_VALUES = [ COPILOT_BILLING_PROTOCOL.legacy, ] as const; +export const COPILOT_VALIDATION_PURPOSE = { +${renderRecord( + validationPurposes.map((value) => { + const name = pascalCase(value) + return { name: name.charAt(0).toLowerCase() + name.slice(1), value } + }) +)} +} as const; + +export const COPILOT_VALIDATION_PURPOSE_VALUES = [ +${validationPurposes.map((value) => ` ${JSON.stringify(value)},`).join('\n')} +] as const; + +export type CopilotValidationPurpose = + (typeof COPILOT_VALIDATION_PURPOSE_VALUES)[number]; + export const BILLING_ATTRIBUTION_HEADER_MAX_BYTES = ${attributionHeaderMaxBytes}; export const BILLING_ACCOUNT_DECISION_HEADER_MAX_BYTES = ${accountDecisionHeaderMaxBytes}; diff --git a/scripts/test-knowledge-acls.ts b/scripts/test-knowledge-acls.ts index b8c00c6fad6..e28ba89c120 100644 --- a/scripts/test-knowledge-acls.ts +++ b/scripts/test-knowledge-acls.ts @@ -150,6 +150,7 @@ try { const environment = { ...process.env, DATABASE_URL: databaseUrl, + MIGRATION_DATABASE_URL: databaseUrl, KNOWLEDGE_ACL_TEST_DATABASE_URL: databaseUrl, KNOWLEDGE_ACL_TEST_REDIS_URL: `redis://${redisEndpoint}`, ...(scale ? { KNOWLEDGE_SCALE_REPORT_FILE: scaleReportFile } : {}), From 212a3a930f2b248d7530ac6892f21bde519734be Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 16 Sep 2026 13:47:11 -0700 Subject: [PATCH 19/43] fix(access-control): keep the settings page open while an organization is governed (#7890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(access-control): keep the settings page open while an organization is governed Permission groups keep applying through a failing payment, but the page that edits them was hidden with the rest of the Enterprise sections — so an organization could be governed by rules nobody could see or loosen until the invoice cleared. Access Control now follows the governance reader rather than the plan gate, on the page, in the navigation, and at the management API, which had already been left behind the plan gate to match the page. Also drops a stray "Open localhost" line from the README's self-hosted quick start; the walkthrough below it already says where to look. * fix(access-control): read the active permission regime, not the raw plan - Resolve the navigation flag rather than reject it: the organization surface is shared by every page, so a failed billing read would have taken home, chat and search down with the settings sidebar - Read the regime helper everywhere instead of the governance reader, so a deployment with Access Control switched off manages nothing - Give the workspace-scoped page the same treatment as the organization one, which was still plan-gated - Read one lookup per section rather than both, since Access Control's availability never consults the plan - Refresh the navigation flag from the billing summary alongside the plan it sits next to, so the item cannot linger after billing changes --- README.md | 2 - .../[id]/permission-groups/utils.test.ts | 22 ++++---- .../[id]/permission-groups/utils.ts | 13 +++-- .../settings/navigation.test.ts | 18 ++++++- .../organization-settings-sidebar.tsx | 15 +++++- .../components/settings/navigation.test.ts | 2 + apps/sim/components/settings/navigation.ts | 17 +++++- apps/sim/lib/organizations/surface.test.ts | 2 + apps/sim/lib/organizations/surface.ts | 54 +++++++++++++------ .../organization-section-access.test.ts | 46 ++++++++++++++++ .../organization-section-access.ts | 17 ++++-- .../workspace-section-access.test.ts | 26 ++++++++- .../application/workspace-section-access.ts | 16 ++++-- 13 files changed, 205 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 78c103799e2..f25af677768 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,6 @@ npx sim-setup ``` -Open [http://localhost:3000](http://localhost:3000) - ### Desktop: [macOS](https://sim.ai/api/desktop/update/download) Download Sim for macOS diff --git a/apps/sim/app/api/organizations/[id]/permission-groups/utils.test.ts b/apps/sim/app/api/organizations/[id]/permission-groups/utils.test.ts index 62c0ddb18cc..c426e067815 100644 --- a/apps/sim/app/api/organizations/[id]/permission-groups/utils.test.ts +++ b/apps/sim/app/api/organizations/[id]/permission-groups/utils.test.ts @@ -4,13 +4,15 @@ import { resetDbChainMock } from '@sim/testing' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' -const { mockIsOrganizationAdminOrOwner, mockIsOrganizationOnEnterprisePlan } = vi.hoisted(() => ({ - mockIsOrganizationAdminOrOwner: vi.fn<() => Promise>(), - mockIsOrganizationOnEnterprisePlan: vi.fn<() => Promise>(), -})) +const { mockIsOrganizationAdminOrOwner, mockIsOrganizationPermissionRegimeActive } = vi.hoisted( + () => ({ + mockIsOrganizationAdminOrOwner: vi.fn<() => Promise>(), + mockIsOrganizationPermissionRegimeActive: vi.fn<() => Promise>(), + }) +) -vi.mock('@/lib/billing', () => ({ - isOrganizationOnEnterprisePlan: mockIsOrganizationOnEnterprisePlan, +vi.mock('@/lib/permission-groups/resolve.server', () => ({ + isOrganizationPermissionRegimeActive: mockIsOrganizationPermissionRegimeActive, })) vi.mock('@/lib/workspaces/permissions/utils', () => ({ @@ -29,7 +31,7 @@ describe('authorizeOrgAccessControl', () => { it('returns a 403 when the user is not an organization admin/owner', async () => { mockIsOrganizationAdminOrOwner.mockResolvedValue(false) - mockIsOrganizationOnEnterprisePlan.mockResolvedValue(true) + mockIsOrganizationPermissionRegimeActive.mockResolvedValue(true) const response = await authorizeOrgAccessControl('user-1', 'org-1') @@ -37,12 +39,12 @@ describe('authorizeOrgAccessControl', () => { expect(response?.status).toBe(403) await expect(response?.json()).resolves.toEqual({ error: 'Admin permissions required' }) // Entitlement is only checked after the admin gate passes. - expect(mockIsOrganizationOnEnterprisePlan).not.toHaveBeenCalled() + expect(mockIsOrganizationPermissionRegimeActive).not.toHaveBeenCalled() }) it('returns a 403 when the organization is not on an enterprise plan', async () => { mockIsOrganizationAdminOrOwner.mockResolvedValue(true) - mockIsOrganizationOnEnterprisePlan.mockResolvedValue(false) + mockIsOrganizationPermissionRegimeActive.mockResolvedValue(false) const response = await authorizeOrgAccessControl('user-1', 'org-1') @@ -54,7 +56,7 @@ describe('authorizeOrgAccessControl', () => { it('returns null when the user is an admin and the org is entitled', async () => { mockIsOrganizationAdminOrOwner.mockResolvedValue(true) - mockIsOrganizationOnEnterprisePlan.mockResolvedValue(true) + mockIsOrganizationPermissionRegimeActive.mockResolvedValue(true) const response = await authorizeOrgAccessControl('user-1', 'org-1') diff --git a/apps/sim/app/api/organizations/[id]/permission-groups/utils.ts b/apps/sim/app/api/organizations/[id]/permission-groups/utils.ts index cb19b17e163..026963a3a5a 100644 --- a/apps/sim/app/api/organizations/[id]/permission-groups/utils.ts +++ b/apps/sim/app/api/organizations/[id]/permission-groups/utils.ts @@ -2,12 +2,12 @@ import { db } from '@sim/db' import { permissionGroup, permissionGroupWorkspace, workspace } from '@sim/db/schema' import { and, asc, eq, inArray } from 'drizzle-orm' import { NextResponse } from 'next/server' -import { isOrganizationOnEnterprisePlan } from '@/lib/billing' import type { DbOrTx } from '@/lib/db/types' import type { AllMembersConflict, ScopeConflict, } from '@/lib/permission-groups/application/group-membership' +import { isOrganizationPermissionRegimeActive } from '@/lib/permission-groups/resolve.server' import { isOrganizationAdminOrOwner } from '@/lib/workspaces/permissions/utils' /** A workspace reference (id + display name). */ @@ -32,13 +32,12 @@ export async function authorizeOrgAccessControl( } /** - * The feature gate, deliberately, not the governance reader: the Access Control settings page is - * gated on the same plan check, so reading governance here would open the API for a past-due - * organization whose page still 404s. Restrictions keep applying through a dunning window — - * that is what the governance reader is for — but managing them follows the page. + * The active permission regime, which is what the Access Control page now reads too: an + * organization whose restrictions still apply has to be able to see and loosen them, and a + * deployment with Access Control switched off governs nobody, so neither should manage anything. */ - const entitled = await isOrganizationOnEnterprisePlan(organizationId) - if (!entitled) { + const governed = await isOrganizationPermissionRegimeActive(organizationId) + if (!governed) { return NextResponse.json({ error: 'Access Control is an Enterprise feature' }, { status: 403 }) } diff --git a/apps/sim/app/o/[organizationId]/settings/navigation.test.ts b/apps/sim/app/o/[organizationId]/settings/navigation.test.ts index bf92bfcb03f..0d82bf2f5fc 100644 --- a/apps/sim/app/o/[organizationId]/settings/navigation.test.ts +++ b/apps/sim/app/o/[organizationId]/settings/navigation.test.ts @@ -18,6 +18,7 @@ import { const enterprise: OrganizationSettingsFeatures = { billingEnabled: true, hasEnterprisePlan: true, + governanceActive: true, hosted: true, selfHosted: {}, } @@ -46,12 +47,27 @@ describe('organization settings navigation', () => { expect( organizationSettingsNavigation( true, - { ...enterprise, hasEnterprisePlan: false }, + { ...enterprise, hasEnterprisePlan: false, governanceActive: false }, available ).map(({ id }) => id) ).toEqual(['billing', 'members', 'recently-deleted', 'search-mcp']) }) + /** + * A failing payment closes the plan gate while the organization's permission groups keep + * applying, so the page that edits them has to stay listed — otherwise its members are governed + * by rules nobody can reach until the invoice clears. + */ + it('keeps Access Control listed while the organization is still governed', () => { + expect( + organizationSettingsNavigation( + true, + { ...enterprise, hasEnterprisePlan: false, governanceActive: true }, + available + ).map(({ id }) => id) + ).toEqual(['billing', 'members', 'recently-deleted', 'access-control', 'search-mcp']) + }) + it('honors individual self-hosted feature flags and hides billing when disabled', () => { expect( organizationSettingsNavigation( diff --git a/apps/sim/app/o/[organizationId]/settings/organization-settings-sidebar.tsx b/apps/sim/app/o/[organizationId]/settings/organization-settings-sidebar.tsx index ab5d7c8de34..6c03c4c8e5a 100644 --- a/apps/sim/app/o/[organizationId]/settings/organization-settings-sidebar.tsx +++ b/apps/sim/app/o/[organizationId]/settings/organization-settings-sidebar.tsx @@ -6,7 +6,10 @@ import { ORGANIZATION_SETTINGS_GROUPS } from '@/components/settings/navigation' import { SettingsSidebar } from '@/components/settings/settings-sidebar' import { isApiClientError } from '@/lib/api/client/errors' import { isEnterprise } from '@/lib/billing/plan-helpers' -import { hasUsableSubscriptionAccess } from '@/lib/billing/subscriptions/utils' +import { + hasPaidSubscriptionStatus, + hasUsableSubscriptionAccess, +} from '@/lib/billing/subscriptions/utils' import { organizationRoutes, WORKSPACE_SETTINGS_PATH } from '@/lib/navigation/paths' import { useOrganizationContext } from '@/app/o/[organizationId]/providers/organization-provider' import { @@ -42,6 +45,16 @@ export function OrganizationSettingsSidebar(props: OrganizationSettingsSidebarPr isEnterprise(summary.data.subscriptionPlan) && hasUsableSubscriptionAccess(summary.data.subscriptionStatus, summary.data.billingBlocked) : settingsFeatures.hasEnterprisePlan, + /** + * Refreshed from the same summary, or the item the plan gate just hid would reappear only on + * reload. Governance keeps its own rule — an entitled status, block state ignored — because a + * failing payment does not stop the organization's permission groups from applying. + */ + governanceActive: + refreshPlan && summary + ? isEnterprise(summary.data.subscriptionPlan) && + hasPaidSubscriptionStatus(summary.data.subscriptionStatus) + : settingsFeatures.governanceActive, } const routes = organizationRoutes(organization.id) diff --git a/apps/sim/components/settings/navigation.test.ts b/apps/sim/components/settings/navigation.test.ts index e469f904048..eab988612ea 100644 --- a/apps/sim/components/settings/navigation.test.ts +++ b/apps/sim/components/settings/navigation.test.ts @@ -231,6 +231,7 @@ describe('settings navigation boundaries', () => { ).toEqual({ billingEnabled: false, hasEnterprisePlan: true, + governanceActive: true, hosted: false, selfHosted: { 'connected-accounts': true, @@ -492,6 +493,7 @@ describe('settings navigation boundaries', () => { const hostedFree = { billingEnabled: true, hasEnterprisePlan: false, + governanceActive: false, hosted: true, selfHosted: {}, } diff --git a/apps/sim/components/settings/navigation.ts b/apps/sim/components/settings/navigation.ts index abd3d5a4111..84f7c7acc0c 100644 --- a/apps/sim/components/settings/navigation.ts +++ b/apps/sim/components/settings/navigation.ts @@ -1014,18 +1014,27 @@ export function resolveOrganizationSectionAccess({ export interface OrganizationSettingsFeatures { billingEnabled: boolean hasEnterprisePlan: boolean + /** + * Whether the organization's permission-group regime is in force, which outlives the plan gate + * through a failing payment — see `isOrganizationGovernanceActive`. Only Access Control reads + * it, because only that section edits something that keeps applying while the gate is closed. + */ + governanceActive: boolean hosted: boolean selfHosted: Partial> } export function getOrganizationSettingsFeatures( hasEnterprisePlan: boolean, - deployment: DeploymentShape + deployment: DeploymentShape, + /** Defaults to the plan gate, so a caller with no reason to distinguish the two keeps its behavior. */ + governanceActive: boolean = hasEnterprisePlan ): OrganizationSettingsFeatures { const { features } = deployment return { billingEnabled: deployment.billingEnabled, hasEnterprisePlan, + governanceActive, hosted: deployment.hosted, selfHosted: { 'connected-accounts': true, @@ -1055,6 +1064,12 @@ export function isOrganizationSettingsSectionAvailable( /* Sim Search itself is enterprise on the hosted product; self-hosted gates it by flag, not by section. */ if (section === 'integrations' || section === 'search-slack') return !features.hosted || features.hasEnterprisePlan + /** + * Access Control follows governance rather than the plan gate: its restrictions keep applying + * through a failing payment, so hiding the page that edits them would leave an organization + * governed by rules it cannot see or loosen until the invoice clears. + */ + if (section === 'access-control' && features.hosted) return features.governanceActive if (features.hosted) return features.hasEnterprisePlan return features.selfHosted[section] ?? false } diff --git a/apps/sim/lib/organizations/surface.test.ts b/apps/sim/lib/organizations/surface.test.ts index 4a622a29d61..1a74e24ae06 100644 --- a/apps/sim/lib/organizations/surface.test.ts +++ b/apps/sim/lib/organizations/surface.test.ts @@ -16,6 +16,8 @@ vi.mock('@/lib/credential-groups/scoped-availability', () => ({ vi.mock('@/lib/permission-groups/resolve.server', () => ({ getUserPermissionConfigForOrganization: mockPermissionConfig, + /** The nav lists Access Control on the regime; these tests drive it from the plan knob. */ + isOrganizationPermissionRegimeActive: mockEnterprisePlan, })) vi.mock('@/lib/billing/core/subscription', () => ({ isOrganizationOnEnterprisePlan: mockEnterprisePlan, diff --git a/apps/sim/lib/organizations/surface.ts b/apps/sim/lib/organizations/surface.ts index ee2c3283c5a..399a97520b0 100644 --- a/apps/sim/lib/organizations/surface.ts +++ b/apps/sim/lib/organizations/surface.ts @@ -18,7 +18,10 @@ import { } from '@/lib/knowledge/access/availability' import { getOrganizationSettingsAccess } from '@/lib/organizations/settings-access' import { capabilityDeniedBy } from '@/lib/permission-groups/capability-assertions' -import { getUserPermissionConfigForOrganization } from '@/lib/permission-groups/resolve.server' +import { + getUserPermissionConfigForOrganization, + isOrganizationPermissionRegimeActive, +} from '@/lib/permission-groups/resolve.server' export interface OrganizationSurfaceOrganization { id: string @@ -77,19 +80,36 @@ async function resolveOrganizationSurfaceContext( if (!row) return null const deployment = getDeploymentShape() - const [config, [{ memberCount }], connectedAccountsAvailable, searchAccess, hasEnterprisePlan] = - await Promise.all([ - getUserPermissionConfigForOrganization(organizationId), - db - .select({ memberCount: count() }) - .from(member) - .where(eq(member.organizationId, organizationId)), - isScopedCredentialGroupsAvailable({ kind: 'organization', organizationId }), - resolveKnowledgeAccessAvailability({ organizationId }), - deployment.hosted && access.isAdmin - ? isOrganizationOnEnterprisePlan(organizationId) - : Promise.resolve(false), - ]) + const [ + config, + [{ memberCount }], + connectedAccountsAvailable, + searchAccess, + hasEnterprisePlan, + governanceActive, + ] = await Promise.all([ + getUserPermissionConfigForOrganization(organizationId), + db + .select({ memberCount: count() }) + .from(member) + .where(eq(member.organizationId, organizationId)), + isScopedCredentialGroupsAvailable({ kind: 'organization', organizationId }), + resolveKnowledgeAccessAvailability({ organizationId }), + deployment.hosted && access.isAdmin + ? isOrganizationOnEnterprisePlan(organizationId) + : Promise.resolve(false), + /** + * Access Control stays listed while a payment is failing, because its rules still apply. + * + * Resolved rather than rejected on a read failure: this value only decides whether a nav item + * is drawn, and it is shared by every organization page — letting it throw would take home, + * chat and search down with the billing table. The page and the management API read the same + * regime and still fail closed, so a listed item cannot be used to reach anything. + */ + deployment.hosted && access.isAdmin + ? isOrganizationPermissionRegimeActive(organizationId).catch(() => false) + : Promise.resolve(false), + ]) return { organization: { id: row.id, @@ -112,7 +132,11 @@ async function resolveOrganizationSurfaceContext( }, connectedAccountsAvailable, searchAccess, - settingsFeatures: getOrganizationSettingsFeatures(hasEnterprisePlan, deployment), + settingsFeatures: getOrganizationSettingsFeatures( + hasEnterprisePlan, + deployment, + governanceActive + ), deployment, } } diff --git a/apps/sim/lib/settings/application/organization-section-access.test.ts b/apps/sim/lib/settings/application/organization-section-access.test.ts index 93602fa53a8..c8640ceffd2 100644 --- a/apps/sim/lib/settings/application/organization-section-access.test.ts +++ b/apps/sim/lib/settings/application/organization-section-access.test.ts @@ -7,6 +7,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ canOpen: vi.fn(), enterprise: vi.fn(), + governance: vi.fn(), groups: vi.fn(), search: vi.fn(), })) @@ -21,6 +22,7 @@ vi.mock('@/lib/organizations/settings-access', () => ({ })) vi.mock('@/lib/billing/core/subscription', () => ({ isOrganizationOnEnterprisePlan: mocks.enterprise, + isOrganizationGovernanceActive: mocks.governance, })) import { authorizeOrganizationSettingsSection } from '@/lib/settings/application/organization-section-access' @@ -31,6 +33,7 @@ describe('organization settings authorization', () => { setEnvFlags({ isHosted: true, isBillingEnabled: true }) mocks.canOpen.mockResolvedValue(true) mocks.enterprise.mockResolvedValue(true) + mocks.governance.mockResolvedValue(true) mocks.groups.mockResolvedValue(true) mocks.search.mockResolvedValue(true) }) @@ -57,6 +60,49 @@ describe('organization settings authorization', () => { } ) + /** + * Access Control configures restrictions that keep applying while a payment is failing, so the + * page that edits them has to stay reachable — otherwise an organization is governed by rules + * nobody can see or loosen until the invoice clears. + */ + it('opens Access Control for an organization still being governed', async () => { + mocks.enterprise.mockResolvedValue(false) + mocks.governance.mockResolvedValue(true) + + await expect( + authorizeOrganizationSettingsSection({ + organizationId: 'target', + userId: 'viewer', + section: 'access-control', + }) + ).resolves.toBe(true) + }) + + it('closes Access Control once nothing governs the organization', async () => { + mocks.enterprise.mockResolvedValue(false) + mocks.governance.mockResolvedValue(false) + + await expect( + authorizeOrganizationSettingsSection({ + organizationId: 'target', + userId: 'viewer', + section: 'access-control', + }) + ).resolves.toBe(false) + }) + + /** Every other section keeps reading the plan gate, and pays no extra lookup for this one. */ + it('reads governance for no section but Access Control', async () => { + await authorizeOrganizationSettingsSection({ + organizationId: 'target', + userId: 'viewer', + section: 'audit-logs', + }) + + expect(mocks.governance).not.toHaveBeenCalled() + expect(mocks.enterprise).toHaveBeenCalledWith('target') + }) + it.each([ { groups: false, search: false, connectedAccounts: false, integrations: false }, { groups: true, search: false, connectedAccounts: true, integrations: false }, diff --git a/apps/sim/lib/settings/application/organization-section-access.ts b/apps/sim/lib/settings/application/organization-section-access.ts index 7427638a686..6e1df504b5b 100644 --- a/apps/sim/lib/settings/application/organization-section-access.ts +++ b/apps/sim/lib/settings/application/organization-section-access.ts @@ -8,6 +8,7 @@ import { getDeploymentShape } from '@/lib/core/config/deployment-shape' import { isScopedCredentialGroupsAvailable } from '@/lib/credential-groups/scoped-availability' import { isKnowledgeMemberAccessAvailable } from '@/lib/knowledge/access/availability' import { canOpenOrganizationSettingsSection } from '@/lib/organizations/settings-access' +import { isOrganizationPermissionRegimeActive } from '@/lib/permission-groups/resolve.server' interface AuthorizeOrganizationSettingsSectionInput { organizationId: string @@ -31,12 +32,20 @@ export async function authorizeOrganizationSettingsSection({ const deployment = getDeploymentShape() const needsEnterprisePlan = deployment.hosted && section !== 'members' && section !== 'billing' - const hasEnterprisePlan = needsEnterprisePlan - ? await isOrganizationOnEnterprisePlan(organizationId) - : false + /** + * Access Control's availability follows the permission regime rather than the plan gate, and no + * other section reads it — so each section pays for exactly one of the two lookups. + */ + const readsRegime = needsEnterprisePlan && section === 'access-control' + const [hasEnterprisePlan, governanceActive] = await Promise.all([ + needsEnterprisePlan && !readsRegime + ? isOrganizationOnEnterprisePlan(organizationId) + : Promise.resolve(false), + readsRegime ? isOrganizationPermissionRegimeActive(organizationId) : Promise.resolve(false), + ]) return isOrganizationSettingsSectionAvailable( section, - getOrganizationSettingsFeatures(hasEnterprisePlan, deployment) + getOrganizationSettingsFeatures(hasEnterprisePlan, deployment, governanceActive) ) } diff --git a/apps/sim/lib/settings/application/workspace-section-access.test.ts b/apps/sim/lib/settings/application/workspace-section-access.test.ts index 248d85372c1..f1378edf865 100644 --- a/apps/sim/lib/settings/application/workspace-section-access.test.ts +++ b/apps/sim/lib/settings/application/workspace-section-access.test.ts @@ -74,6 +74,10 @@ vi.mock('@/lib/credential-groups/scoped-availability', () => ({ vi.mock('@/lib/knowledge/access/availability', () => ({ isKnowledgeMemberAccessAvailable: mocks.isKnowledgeMemberAccessAvailable, })) +vi.mock('@/lib/permission-groups/resolve.server', () => ({ + /** Access Control follows the regime; these tests drive it from the same plan knob. */ + isOrganizationPermissionRegimeActive: mocks.isOrganizationOnEnterprisePlan, +})) vi.mock('@/lib/organizations/settings-access', () => ({ canOpenOrganizationSettingsSection: mocks.canOpenOrganizationSettingsSection, })) @@ -277,7 +281,27 @@ describe('authorizeWorkspaceSettingsSection', () => { mocks.checkWorkspaceAccess.mockResolvedValue(ORGANIZATION_ACCESS) await expect(authorize('access-control')).resolves.toEqual({ allowed: true }) - expect(mocks.getOrganizationSettingsFeatures).toHaveBeenCalledWith(true, mocks.deploymentShape) + /** + * Access Control is gated on the permission regime rather than the plan, so the plan lookup is + * skipped for it and the regime is what reaches the navigation gate. + */ + expect(mocks.getOrganizationSettingsFeatures).toHaveBeenCalledWith( + false, + mocks.deploymentShape, + true + ) + }) + + /** + * The workspace-scoped page reads the same regime as the organization one: an organization whose + * restrictions still apply during a failing payment must not have this page taken away. + */ + it('keeps the workspace Access Control page open while the organization is governed', async () => { + mocks.checkWorkspaceAccess.mockResolvedValue(ORGANIZATION_ACCESS) + mocks.isOrganizationOnEnterprisePlan.mockResolvedValue(false) + + await expect(authorize('access-control')).resolves.toEqual({ allowed: true }) + expect(mocks.isOrganizationOnEnterprisePlan).toHaveBeenCalledTimes(1) }) it('resolves the exact entitlement source only for gated workspace sections', async () => { diff --git a/apps/sim/lib/settings/application/workspace-section-access.ts b/apps/sim/lib/settings/application/workspace-section-access.ts index 2f441121f74..01cca335dfe 100644 --- a/apps/sim/lib/settings/application/workspace-section-access.ts +++ b/apps/sim/lib/settings/application/workspace-section-access.ts @@ -14,6 +14,7 @@ import { getDeploymentShape } from '@/lib/core/config/deployment-shape' import { canOpenOrganizationSettingsSection } from '@/lib/organizations/settings-access' import { isAccessRequestEnabled } from '@/lib/permission-access-requests/settings' import type { BooleanPermissionGroupConfigKey } from '@/lib/permission-groups/features' +import { isOrganizationPermissionRegimeActive } from '@/lib/permission-groups/resolve.server' import { isPlatformAdmin } from '@/lib/permissions/super-user' import { authorizeOrganizationSettingsSection } from '@/lib/settings/application/organization-section-access' import { isCustomBlocksEligibleForOrganization } from '@/lib/workflows/custom-blocks/operations' @@ -114,17 +115,26 @@ async function canOpenOrganizationSection( } const needsEnterprisePlan = organizationSection !== 'members' && organizationSection !== 'billing' - const [canOpenSection, isEnterpriseOrganization] = await Promise.all([ + /** Same split as the organization surface: Access Control follows the regime, everything else the plan. */ + const readsRegime = needsEnterprisePlan && organizationSection === 'access-control' + const [canOpenSection, isEnterpriseOrganization, governanceActive] = await Promise.all([ canOpenOrganizationSettingsSection(workspace.organizationId, input.userId, organizationSection), - needsEnterprisePlan + needsEnterprisePlan && !readsRegime ? isOrganizationOnEnterprisePlan(workspace.organizationId) : Promise.resolve(false), + readsRegime + ? isOrganizationPermissionRegimeActive(workspace.organizationId) + : Promise.resolve(false), ]) return ( canOpenSection && isOrganizationSettingsSectionAvailable( organizationSection, - getOrganizationSettingsFeatures(needsEnterprisePlan && isEnterpriseOrganization, deployment) + getOrganizationSettingsFeatures( + needsEnterprisePlan && isEnterpriseOrganization, + deployment, + governanceActive + ) ) ) } From 55ccc5dfa3723e5032c49ad18af3d1bbe897453d Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 16 Sep 2026 14:08:27 -0700 Subject: [PATCH 20/43] feat(search-mcp): persist client-attributed tool activity (#7892) * feat(search-mcp): persist client-attributed tool activity * fix(search-mcp): snapshot client attribution during admission --- apps/sim/lib/auth/oauth-access-token.test.ts | 9 + apps/sim/lib/auth/oauth-access-token.ts | 4 +- apps/sim/lib/auth/oauth-principal.test.ts | 8 + .../organization-mcp-search.integration.ts | 85 +- apps/sim/lib/knowledge/mcp/activity.test.ts | 87 + apps/sim/lib/knowledge/mcp/activity.ts | 48 + apps/sim/lib/knowledge/mcp/server.test.ts | 144 +- apps/sim/lib/knowledge/mcp/server.ts | 122 +- packages/auth/src/principal.ts | 9 +- .../migrations/0353_search_mcp_activity.sql | 23 + .../db/migrations/meta/0353_snapshot.json | 27230 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 58 + packages/logger/src/index.test.ts | 69 +- packages/logger/src/index.ts | 4 +- packages/testing/src/mocks/schema.mock.ts | 12 + 16 files changed, 27868 insertions(+), 51 deletions(-) create mode 100644 apps/sim/lib/knowledge/mcp/activity.test.ts create mode 100644 apps/sim/lib/knowledge/mcp/activity.ts create mode 100644 packages/db/migrations/0353_search_mcp_activity.sql create mode 100644 packages/db/migrations/meta/0353_snapshot.json diff --git a/apps/sim/lib/auth/oauth-access-token.test.ts b/apps/sim/lib/auth/oauth-access-token.test.ts index 48d7bdb668c..7af7ad3b809 100644 --- a/apps/sim/lib/auth/oauth-access-token.test.ts +++ b/apps/sim/lib/auth/oauth-access-token.test.ts @@ -18,6 +18,7 @@ function row(overrides: Record = {}) { id: 'token-1', userId: 'user-1', clientId: 'sim-cli', + clientName: 'Sim CLI', scopes: ['offline_access', 'api:read'], resource: null, expiresAt: new Date(Date.now() + 60_000), @@ -80,6 +81,7 @@ describe('verifyOAuthAccessToken', () => { kind: 'oauth_access_token', userId: 'user-1', clientId: 'sim-cli', + clientName: 'Sim CLI', tokenId: 'token-1', scopes: ['offline_access', 'api:read'], expiresAt: expect.any(Date), @@ -92,6 +94,13 @@ describe('verifyOAuthAccessToken', () => { ) }) + it('does not invent a display name for an unnamed OAuth client', async () => { + queueTableRows(schemaMock.oauthAccessToken, [row({ clientName: null })]) + const principal = await verifyOAuthAccessToken('sim_oat_secret') + expect(principal).not.toHaveProperty('clientName') + expect(principal.clientId).toBe('sim-cli') + }) + it('refuses a credential that is not one of ours without a database read', async () => { expect(await reason('sim_abc')).toBe('malformed') expect(await reason('sim_oat_')).toBe('malformed') diff --git a/apps/sim/lib/auth/oauth-access-token.ts b/apps/sim/lib/auth/oauth-access-token.ts index 87441af091d..4204d4fb80e 100644 --- a/apps/sim/lib/auth/oauth-access-token.ts +++ b/apps/sim/lib/auth/oauth-access-token.ts @@ -3,7 +3,7 @@ import { db } from '@sim/db' import { oauthAccessToken, oauthClient, user } from '@sim/db/schema' import { createLogger, setRequestAuth } from '@sim/logger' import { sha256Hex } from '@sim/security/hash' -import { eq } from 'drizzle-orm' +import { eq, sql } from 'drizzle-orm' import { isAccountBlocked } from '@/lib/auth/ban' import { OAUTH_ACCESS_TOKEN_PREFIX, @@ -99,6 +99,7 @@ export async function verifyOAuthAccessToken( id: oauthAccessToken.id, userId: oauthAccessToken.userId, clientId: oauthAccessToken.clientId, + clientName: sql`left(${oauthClient.name}, 256)`, scopes: oauthAccessToken.scopes, resource: oauthAccessToken.resource, expiresAt: oauthAccessToken.expiresAt, @@ -142,6 +143,7 @@ export async function verifyOAuthAccessToken( kind: 'oauth_access_token', userId: row.userId, clientId: row.clientId, + ...(row.clientName ? { clientName: row.clientName } : {}), tokenId: row.id, scopes: row.scopes, expiresAt: row.expiresAt, diff --git a/apps/sim/lib/auth/oauth-principal.test.ts b/apps/sim/lib/auth/oauth-principal.test.ts index de3ef929e68..bca0bd6af09 100644 --- a/apps/sim/lib/auth/oauth-principal.test.ts +++ b/apps/sim/lib/auth/oauth-principal.test.ts @@ -66,4 +66,12 @@ describe('oauth_access_token principal', () => { parsePrincipal({ ...serialized, principal: { ...serialized.principal, expiresAt: 'soon' } }) ).toThrow('expiresAt must be an ISO timestamp') }) + + it('keeps display metadata out of persisted workflow authority', () => { + const named = { ...principal, clientName: 'Registered app' } + const serialized = serializePrincipal(named) + expect(serialized.principal).not.toHaveProperty('clientName') + expect(parsePrincipal(serialized)).toEqual(principal) + expect(toPrincipalActor(named)).toEqual(toPrincipalActor(principal)) + }) }) diff --git a/apps/sim/lib/knowledge/__integration__/organization-mcp-search.integration.ts b/apps/sim/lib/knowledge/__integration__/organization-mcp-search.integration.ts index 8f8374ebb9f..1f1c1808abd 100644 --- a/apps/sim/lib/knowledge/__integration__/organization-mcp-search.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/organization-mcp-search.integration.ts @@ -26,6 +26,7 @@ import { oauthConsent, organization, organizationSearchIntegration, + organizationSearchMcpInvocation, rateLimitBucket, user, workspace, @@ -35,9 +36,19 @@ import { generateId } from '@sim/utils/id' import { isPlainRecord } from '@sim/utils/object' import { and, eq, inArray } from 'drizzle-orm' import { NextRequest } from 'next/server' -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' +import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest' -const fixtures = vi.hoisted(() => ({ storageRoot: '' })) +const fixtures = vi.hoisted(() => ({ + storageRoot: '', + afterResponse: [] as Array<() => Promise>, +})) +vi.mock('@/lib/core/utils/after-response', () => ({ + afterResponse: (task: () => Promise) => fixtures.afterResponse.push(task), +})) + +async function flushAfterResponse() { + for (const task of fixtures.afterResponse.splice(0)) await task() +} vi.mock('@/lib/uploads/core/setup.server', () => ({ get UPLOAD_DIR_SERVER() { return fixtures.storageRoot @@ -401,6 +412,8 @@ describe('organization Search MCP with real ingestion and current access', () => bobOAuth = await connect(OAUTH_ACCESS_TOKEN_PREFIX + oauthTokens.bob, true) }) + afterEach(flushAfterResponse) + afterAll(async () => { await Promise.all(clients.map((client) => client.close())) await db.delete(oauthClient).where(eq(oauthClient.clientId, oauthClientId)) @@ -508,6 +521,74 @@ describe('organization Search MCP with real ingestion and current access', () => expect(await applicationSearch(bobPrincipal)).toEqual([]) }) + it('persists content-free per-client tool outcomes separately from search counters', async () => { + await db + .delete(organizationSearchMcpInvocation) + .where(eq(organizationSearchMcpInvocation.organizationId, organizationId)) + const clientName = 'MCP fixture client'.repeat(20) + await db + .update(oauthClient) + .set({ name: clientName }) + .where(eq(oauthClient.clientId, oauthClientId)) + try { + await aliceOAuth.listTools() + expect(fixtures.afterResponse).toHaveLength(0) + await search(aliceOAuth) + await value(aliceOAuth, 'read_document', { documentId }) + expect((await call(bob, 'read_document', { documentId })).isError).toBe(true) + expect(fixtures.afterResponse).toHaveLength(3) + await db + .update(oauthClient) + .set({ name: 'Renamed client' }) + .where(eq(oauthClient.clientId, oauthClientId)) + await flushAfterResponse() + const rows = await db + .select() + .from(organizationSearchMcpInvocation) + .where(eq(organizationSearchMcpInvocation.organizationId, organizationId)) + .orderBy(organizationSearchMcpInvocation.createdAt) + .limit(10) + expect(rows).toHaveLength(3) + expect(rows).toMatchObject([ + { + organizationId, + userId: aliceId, + authKind: 'oauth_access_token', + oauthClientId, + clientName: clientName.slice(0, 256), + toolName: 'search', + outcome: 'success', + }, + { + organizationId, + userId: aliceId, + authKind: 'oauth_access_token', + oauthClientId, + clientName: clientName.slice(0, 256), + toolName: 'read_document', + outcome: 'success', + }, + { + organizationId, + userId: bobId, + authKind: 'personal_api_key', + oauthClientId: null, + clientName: null, + toolName: 'read_document', + outcome: 'error', + }, + ]) + expect(rows.every((row) => row.durationMs >= 0)).toBe(true) + expect(JSON.stringify(rows)).not.toContain(documentId) + expect(JSON.stringify(rows)).not.toContain(oauthTokens.alice) + } finally { + await db + .update(oauthClient) + .set({ name: 'Search MCP OAuth fixture' }) + .where(eq(oauthClient.clientId, oauthClientId)) + } + }) + it('enforces current document and organization access on Search OAuth clients', async () => { expect((await aliceOAuth.listTools()).tools).toHaveLength(3) expect(await search(aliceOAuth)).toEqual(await search(alice)) diff --git a/apps/sim/lib/knowledge/mcp/activity.test.ts b/apps/sim/lib/knowledge/mcp/activity.test.ts new file mode 100644 index 00000000000..0da06d56a05 --- /dev/null +++ b/apps/sim/lib/knowledge/mcp/activity.test.ts @@ -0,0 +1,87 @@ +/** @vitest-environment node */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const mocks = vi.hoisted(() => ({ + values: vi.fn(), + insert: vi.fn(), + execute: vi.fn(), + transaction: vi.fn(), +})) +vi.mock('@sim/db', () => ({ db: { transaction: mocks.transaction } })) + +import { + recordOrganizationSearchMcpActivity, + type SearchMcpActivityInput, +} from '@/lib/knowledge/mcp/activity' + +const activity: SearchMcpActivityInput = { + organizationId: 'org', + userId: 'actor', + authKind: 'personal_api_key', + oauthClientId: null, + clientName: null, + toolName: 'read_document', + outcome: 'success', + durationMs: 42, + createdAt: new Date('2026-01-01T00:00:00Z'), +} + +beforeEach(() => { + vi.clearAllMocks() + mocks.insert.mockReturnValue({ values: mocks.values }) + mocks.values.mockResolvedValue(undefined) + mocks.execute.mockResolvedValue(undefined) + mocks.transaction.mockImplementation((callback) => + callback({ execute: mocks.execute, insert: mocks.insert }) + ) +}) + +describe('persistent MCP activity', () => { + it('stores an API-key call without inventing an application name', async () => { + await recordOrganizationSearchMcpActivity(activity) + expect(mocks.values).toHaveBeenCalledExactlyOnceWith({ + id: expect.any(String), + ...activity, + clientName: null, + }) + }) + + it('only persists the allowlisted metadata when extra content is present', async () => { + const input = { + ...activity, + query: 'private question', + content: 'private document', + token: 'private token', + } + await recordOrganizationSearchMcpActivity(input) + expect(mocks.values).toHaveBeenCalledExactlyOnceWith({ + id: expect.any(String), + ...activity, + clientName: null, + }) + }) + + it('sets the transaction deadline before attempting the insert', async () => { + const ready = Promise.withResolvers() + mocks.execute.mockReturnValueOnce(ready.promise) + const recording = recordOrganizationSearchMcpActivity(activity) + expect(mocks.insert).not.toHaveBeenCalled() + expect(JSON.stringify(mocks.execute.mock.calls[0])).toContain( + "SET LOCAL statement_timeout = '2s'" + ) + ready.resolve() + await recording + expect(mocks.insert).toHaveBeenCalledOnce() + }) + + it('does not insert when the deadline could not be established', async () => { + mocks.execute.mockRejectedValueOnce(new Error('unavailable')) + await expect(recordOrganizationSearchMcpActivity(activity)).resolves.toBeUndefined() + expect(mocks.insert).not.toHaveBeenCalled() + }) + + it('does not propagate storage failures into the request lifecycle', async () => { + mocks.values.mockRejectedValueOnce(new Error('offline')) + await expect(recordOrganizationSearchMcpActivity(activity)).resolves.toBeUndefined() + }) +}) diff --git a/apps/sim/lib/knowledge/mcp/activity.ts b/apps/sim/lib/knowledge/mcp/activity.ts new file mode 100644 index 00000000000..92ae188431b --- /dev/null +++ b/apps/sim/lib/knowledge/mcp/activity.ts @@ -0,0 +1,48 @@ +import { db } from '@sim/db' +import { organizationSearchMcpInvocation } from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' +import { sql } from 'drizzle-orm' + +const logger = createLogger('OrganizationSearchMcpActivity') + +export type SearchMcpActivityInput = Pick< + typeof organizationSearchMcpInvocation.$inferInsert, + | 'organizationId' + | 'userId' + | 'authKind' + | 'oauthClientId' + | 'clientName' + | 'toolName' + | 'outcome' + | 'durationMs' + | 'createdAt' +> + +/** Stores content-free metadata from an admitted MCP request, independently of tool success. */ +export async function recordOrganizationSearchMcpActivity( + input: SearchMcpActivityInput +): Promise { + try { + await db.transaction(async (tx) => { + await tx.execute(sql`SET LOCAL statement_timeout = '2s'`) + await tx.insert(organizationSearchMcpInvocation).values({ + id: generateId(), + organizationId: input.organizationId, + userId: input.userId, + authKind: input.authKind, + oauthClientId: input.oauthClientId, + clientName: input.clientName, + toolName: input.toolName, + outcome: input.outcome, + durationMs: input.durationMs, + createdAt: input.createdAt, + }) + }) + } catch (error) { + logger.warn('Failed to record organization Search MCP activity', { + error: getErrorMessage(error), + }) + } +} diff --git a/apps/sim/lib/knowledge/mcp/server.test.ts b/apps/sim/lib/knowledge/mcp/server.test.ts index 180f55fd121..c36904b77c1 100644 --- a/apps/sim/lib/knowledge/mcp/server.test.ts +++ b/apps/sim/lib/knowledge/mcp/server.test.ts @@ -1,5 +1,6 @@ /** @vitest-environment node */ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js' +import { createMockLogger } from '@sim/testing' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -12,6 +13,16 @@ const mocks = vi.hoisted(() => ({ read: vi.fn(), chat: vi.fn(), rateLimit: vi.fn(), + info: vi.fn(), + afterResponse: vi.fn<(task: () => Promise) => void>(), + recordActivity: vi.fn(), +})) +vi.mock('@/lib/core/utils/after-response', () => ({ afterResponse: mocks.afterResponse })) +vi.mock('@/lib/knowledge/mcp/activity', () => ({ + recordOrganizationSearchMcpActivity: mocks.recordActivity, +})) +vi.mock('@sim/logger', () => ({ + createLogger: () => ({ ...createMockLogger(), info: mocks.info }), })) vi.mock('@modelcontextprotocol/sdk/server/mcp.js', () => ({ McpServer: class { @@ -71,7 +82,7 @@ function payload(result: CallToolResult): unknown { beforeEach(() => { vi.clearAllMocks() mocks.tools.clear() - mocks.rateLimit.mockResolvedValue(null) + mocks.rateLimit.mockReset().mockResolvedValue(null) mocks.search.mockResolvedValue({ results: [] }) mocks.read.mockResolvedValue({ knowledgeBaseId: 'index-1', @@ -314,3 +325,134 @@ describe('organization chat', () => { }) }) }) + +describe('MCP tool completion records', () => { + it('schedules only metadata after the response, independently of analytics storage latency', async () => { + createKnowledgeMcpServer({ + organizationId: 'org-1', + searchIndexId: 'index-1', + request, + auth: { + ...auth, + keyType: 'oauth_access_token', + principal: { + kind: 'oauth_access_token', + userId: 'oauth-person', + clientId: 'registered-client', + clientName: 'Registered app', + tokenId: 'private-token-id', + scopes: ['search:read'], + expiresAt: new Date(Date.now() + 60000), + }, + }, + }) + const response = await call('search', { query: 'private question' }) + expect(response.isError).not.toBe(true) + expect(mocks.recordActivity).not.toHaveBeenCalled() + expect(mocks.afterResponse).toHaveBeenCalledOnce() + await mocks.afterResponse.mock.calls[0][0]() + expect(mocks.recordActivity).toHaveBeenCalledExactlyOnceWith({ + organizationId: 'org-1', + userId: 'oauth-person', + authKind: 'oauth_access_token', + oauthClientId: 'registered-client', + clientName: 'Registered app', + toolName: 'search', + outcome: 'success', + durationMs: expect.any(Number), + createdAt: expect.any(Date), + }) + }) + + it.each([ + ['search', { query: 'private query', topK: 10 }, 'knowledge.search'], + ['read_document', { documentId: 'doc-1' }, 'knowledge.documents.read'], + ['chat', { query: 'private question' }, 'knowledge.chat'], + ] as const)('records one content-free completion for %s', async (toolName, input, operation) => { + create() + await call(toolName, input) + expect(mocks.info).toHaveBeenCalledExactlyOnceWith('Knowledge MCP tool completed', { + toolName, + operation, + organizationId: 'org-1', + userId: 'person-1', + outcome: 'success', + durationMs: expect.any(Number), + }) + }) + + it('records a returned tool error as an error even though the HTTP transport can succeed', async () => { + create() + const result = await call('read_document', {}) + expect(result.isError).toBe(true) + expect(mocks.info).toHaveBeenCalledExactlyOnceWith( + 'Knowledge MCP tool completed', + expect.objectContaining({ toolName: 'read_document', outcome: 'error' }) + ) + }) + + it('records an authorization failure without including the query or error message', async () => { + create() + mocks.search.mockRejectedValueOnce(new OrchestrationError('forbidden', 'Private denial reason')) + await call('search', { query: 'private query' }) + expect(mocks.info).toHaveBeenCalledExactlyOnceWith('Knowledge MCP tool completed', { + toolName: 'search', + operation: 'knowledge.search', + organizationId: 'org-1', + userId: 'person-1', + outcome: 'error', + durationMs: expect.any(Number), + }) + }) + + it('distinguishes rate limiting from an executed tool', async () => { + create() + mocks.rateLimit.mockResolvedValueOnce(new Response(null, { status: 429 })) + await call('search', { query: 'private query' }) + expect(mocks.search).not.toHaveBeenCalled() + expect(mocks.info).toHaveBeenCalledExactlyOnceWith( + 'Knowledge MCP tool completed', + expect.objectContaining({ outcome: 'rate_limited' }) + ) + await mocks.afterResponse.mock.calls[0][0]() + expect(mocks.recordActivity).toHaveBeenCalledWith( + expect.objectContaining({ + authKind: 'personal_api_key', + oauthClientId: null, + outcome: 'rate_limited', + }) + ) + }) + + it.each(['search', 'read_document', 'chat'])( + 'records cancelled %s calls without executing the operation', + async (toolName) => { + create() + mocks.rateLimit.mockResolvedValueOnce(new Response(null, { status: 429 })) + await call(toolName, { query: 'private query', documentId: 'doc-1' }, AbortSignal.abort()) + expect(mocks.rateLimit).not.toHaveBeenCalled() + expect(mocks.search).not.toHaveBeenCalled() + expect(mocks.read).not.toHaveBeenCalled() + expect(mocks.chat).not.toHaveBeenCalled() + expect(mocks.info).toHaveBeenCalledExactlyOnceWith( + 'Knowledge MCP tool completed', + expect.objectContaining({ toolName, outcome: 'cancelled' }) + ) + } + ) + + it('records cancellation during rate-limit admission instead of an exhausted bucket', async () => { + create() + const controller = new AbortController() + mocks.rateLimit.mockImplementationOnce(async () => { + controller.abort() + return new Response(null, { status: 429 }) + }) + await call('search', { query: 'private query' }, controller.signal) + expect(mocks.search).not.toHaveBeenCalled() + await mocks.afterResponse.mock.calls[0][0]() + expect(mocks.recordActivity).toHaveBeenCalledWith( + expect.objectContaining({ outcome: 'cancelled' }) + ) + }) +}) diff --git a/apps/sim/lib/knowledge/mcp/server.ts b/apps/sim/lib/knowledge/mcp/server.ts index 477ca7fb744..226fece5474 100644 --- a/apps/sim/lib/knowledge/mcp/server.ts +++ b/apps/sim/lib/knowledge/mcp/server.ts @@ -1,5 +1,6 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js' +import { resolvePrincipalSubjectUserId } from '@sim/auth/principal' import { createLogger } from '@sim/logger' import { isPlainRecord } from '@sim/utils/object' import { parseRetryAfter } from '@sim/utils/retry' @@ -13,11 +14,16 @@ import type { V2ApiKeyAuthContext } from '@/lib/api/server/routes/v2-api-key-aut import { v2RateLimits } from '@/lib/api/server/routes/v2-json-route' import type { ApplicationOperation } from '@/lib/core/application' import type { ResourceScope } from '@/lib/core/resource-scope' +import { afterResponse } from '@/lib/core/utils/after-response' import { getBaseUrl } from '@/lib/core/utils/urls' import { organizationSearchChatOperation } from '@/lib/knowledge/application/chat-operations' import { knowledgeOperations } from '@/lib/knowledge/application/operations' import { readIndexedKnowledgeDocument } from '@/lib/knowledge/application/read-indexed-document' import { searchKnowledge } from '@/lib/knowledge/application/search' +import { + recordOrganizationSearchMcpActivity, + type SearchMcpActivityInput, +} from '@/lib/knowledge/mcp/activity' import { createKnowledgeDocumentCitation } from '@/lib/knowledge/search/citation' import { v2CaughtOrchestrationError } from '@/app/api/v2/lib/response' import { projectResolvedSecretModelContent } from '@/executor/utils/resolved-secret-content-projection' @@ -68,12 +74,20 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe const server = new McpServer({ name: 'Sim Search', version: '1.0.0' }) async function execute( + toolName: SearchMcpActivityInput['toolName'], operation: ApplicationOperation, - run: (registry: ResolvedSecretTraceRegistry) => Promise + toolSignal: AbortSignal, + run: (registry: ResolvedSecretTraceRegistry, signal: AbortSignal) => Promise ): Promise { + const startedAt = performance.now() + const signal = AbortSignal.any([request.signal, toolSignal]) + let outcome: SearchMcpActivityInput['outcome'] = 'error' try { + signal.throwIfAborted() const limited = await v2RateLimits.publicApi.enforce(request, auth, operation) + signal.throwIfAborted() if (limited) { + outcome = 'rate_limited' const retryAfter = parseRetryAfter( limited.headers.get('Retry-After'), Number.MAX_SAFE_INTEGER @@ -84,9 +98,11 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe : `API rate limit exceeded. Retry in ${Math.ceil(retryAfter / 1000)} seconds.` ) } - request.signal.throwIfAborted() - return await run(new ResolvedSecretTraceRegistry()) + const result = await run(new ResolvedSecretTraceRegistry(), signal) + outcome = signal.aborted ? 'cancelled' : result.isError ? 'error' : 'success' + return result } catch (error) { + if (signal.aborted) outcome = 'cancelled' const response = v2CaughtOrchestrationError(error) if (response) { const body: unknown = await response.json() @@ -100,6 +116,27 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe } logger.error('Knowledge MCP operation failed', { operation: operation.id, error }) return toolError('Unable to complete this operation. Please try again.') + } finally { + const activity: SearchMcpActivityInput = { + toolName, + organizationId, + userId: resolvePrincipalSubjectUserId(principal) ?? null, + authKind: principal.kind, + oauthClientId: principal.kind === 'oauth_access_token' ? principal.clientId : null, + clientName: principal.kind === 'oauth_access_token' ? (principal.clientName ?? null) : null, + outcome, + durationMs: Math.round(performance.now() - startedAt), + createdAt: new Date(), + } + logger.info('Knowledge MCP tool completed', { + toolName, + operation: operation.id, + organizationId, + userId: activity.userId, + outcome, + durationMs: activity.durationMs, + }) + afterResponse(() => recordOrganizationSearchMcpActivity(activity)) } } @@ -113,7 +150,7 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe annotations: READ_ONLY, }, async ({ query, topK, ...filters }, extra) => - execute(knowledgeOperations.search, async (registry) => { + execute('search', knowledgeOperations.search, extra.signal, async (registry, signal) => { if (!searchIndexId) { return projectResult( { @@ -133,7 +170,7 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe filters, resultSecretRegistry: registry, surface: 'mcp', - signal: AbortSignal.any([request.signal, extra.signal]), + signal, }, request, }) @@ -171,40 +208,43 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe annotations: READ_ONLY, }, async (input, extra) => - execute(knowledgeOperations.readDocument, async (registry) => { - const signal = AbortSignal.any([request.signal, extra.signal]) - signal.throwIfAborted() - if (!input.url && !input.documentId) return toolError('Document not found') - const result = await readIndexedKnowledgeDocument.execute({ - principal, - input: { - organizationId, - target: input.url - ? { kind: 'url', url: input.url } - : { kind: 'id', documentId: input.documentId! }, - limit: input.limit, - offset: input.offset, - aroundChunkIndex: input.aroundChunkIndex, - resultSecretRegistry: registry, - signal, - }, - request, - }) - const { knowledgeBaseId, ...document } = result - return projectResult( - { - ...document, - ...createKnowledgeDocumentCitation({ - scope, - knowledgeBaseId, - documentId: result.documentId, - sourceUrl: result.sourceUrl, - baseUrl: getBaseUrl(), - }), - }, - registry - ) - }) + execute( + 'read_document', + knowledgeOperations.readDocument, + extra.signal, + async (registry, signal) => { + if (!input.url && !input.documentId) return toolError('Document not found') + const result = await readIndexedKnowledgeDocument.execute({ + principal, + input: { + organizationId, + target: input.url + ? { kind: 'url', url: input.url } + : { kind: 'id', documentId: input.documentId! }, + limit: input.limit, + offset: input.offset, + aroundChunkIndex: input.aroundChunkIndex, + resultSecretRegistry: registry, + signal, + }, + request, + }) + const { knowledgeBaseId, ...document } = result + return projectResult( + { + ...document, + ...createKnowledgeDocumentCitation({ + scope, + knowledgeBaseId, + documentId: result.documentId, + sourceUrl: result.sourceUrl, + baseUrl: getBaseUrl(), + }), + }, + registry + ) + } + ) ) server.registerTool( @@ -222,9 +262,7 @@ export function createKnowledgeMcpServer(context: KnowledgeMcpContext): McpServe }, }, async ({ query, ...filters }, extra) => - execute(organizationSearchChatOperation, async (registry) => { - const signal = AbortSignal.any([request.signal, extra.signal]) - signal.throwIfAborted() + execute('chat', organizationSearchChatOperation, extra.signal, async (registry, signal) => { const { organizationSearchChat } = await import('@/lib/knowledge/application/chat') const result = await organizationSearchChat.execute({ principal, diff --git a/packages/auth/src/principal.ts b/packages/auth/src/principal.ts index 17a0e0806e8..99bcd7ca1c5 100644 --- a/packages/auth/src/principal.ts +++ b/packages/auth/src/principal.ts @@ -52,6 +52,8 @@ export interface OAuthAccessTokenPrincipal { kind: 'oauth_access_token' userId: string clientId: string + /** Admission-time display metadata only; never grants authority or enters workflow payloads. */ + clientName?: string /** The `oauth_access_token` row id, never the token itself. */ tokenId: string scopes: readonly string[] @@ -291,7 +293,7 @@ export type WorkflowExecutionPrincipal = type SerializedWorkflowExecutionPrincipal = | SessionPrincipal | PersonalApiKeyPrincipal - | (Omit & { expiresAt: string }) + | (Omit & { expiresAt: string }) | WorkspaceApiKeyPrincipal | SystemPrincipal | (Omit & { @@ -398,7 +400,10 @@ export function serializePrincipal(principal: WorkflowExecutionPrincipal): Seria return { version: 1, principal: { - ...principal, + kind: principal.kind, + userId: principal.userId, + clientId: principal.clientId, + tokenId: principal.tokenId, scopes: [...principal.scopes], expiresAt: principal.expiresAt.toISOString(), }, diff --git a/packages/db/migrations/0353_search_mcp_activity.sql b/packages/db/migrations/0353_search_mcp_activity.sql new file mode 100644 index 00000000000..75e8a217e63 --- /dev/null +++ b/packages/db/migrations/0353_search_mcp_activity.sql @@ -0,0 +1,23 @@ +CREATE TABLE "organization_search_mcp_invocation" ( + "id" text PRIMARY KEY NOT NULL, + "organization_id" text NOT NULL, + "user_id" text, + "auth_kind" text NOT NULL, + "oauth_client_id" text, + "client_name" text, + "tool_name" text NOT NULL, + "outcome" text NOT NULL, + "duration_ms" integer NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "organization_search_mcp_invocation_tool_check" CHECK ("organization_search_mcp_invocation"."tool_name" IN ('search', 'read_document', 'chat')), + CONSTRAINT "organization_search_mcp_invocation_outcome_check" CHECK ("organization_search_mcp_invocation"."outcome" IN ('success', 'error', 'cancelled', 'rate_limited')), + CONSTRAINT "organization_search_mcp_invocation_duration_check" CHECK ("organization_search_mcp_invocation"."duration_ms" >= 0), + CONSTRAINT "organization_search_mcp_invocation_client_name_check" CHECK (length("organization_search_mcp_invocation"."client_name") <= 256), + CONSTRAINT "organization_search_mcp_invocation_auth_check" CHECK (("organization_search_mcp_invocation"."auth_kind" = 'oauth_access_token' AND "organization_search_mcp_invocation"."oauth_client_id" IS NOT NULL) + 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)) +); +--> statement-breakpoint +ALTER TABLE "organization_search_mcp_invocation" ADD CONSTRAINT "org_search_mcp_invocation_org_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "organization_search_mcp_invocation" ADD CONSTRAINT "org_search_mcp_invocation_user_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "organization_search_mcp_invocation_org_created_idx" ON "organization_search_mcp_invocation" USING btree ("organization_id","created_at");--> statement-breakpoint +CREATE INDEX "organization_search_mcp_invocation_user_idx" ON "organization_search_mcp_invocation" USING btree ("user_id"); \ No newline at end of file diff --git a/packages/db/migrations/meta/0353_snapshot.json b/packages/db/migrations/meta/0353_snapshot.json new file mode 100644 index 00000000000..4878fae25c7 --- /dev/null +++ b/packages/db/migrations/meta/0353_snapshot.json @@ -0,0 +1,27230 @@ +{ + "id": "40afdacd-8e2e-4cc8-8341-a06f09488b70", + "prevId": "77bb7f33-ce24-4df1-a756-94984877c940", + "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.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 + }, + "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_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_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" + } + }, + "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.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_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_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_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_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": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "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_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_vector_hnsw_idx": { + "name": "embedding_384_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_vector_hnsw_idx": { + "name": "embedding_768_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_vector_hnsw_idx": { + "name": "embedding_1024_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_vector_hnsw_idx": { + "name": "embedding_3072_vector_hnsw_idx", + "columns": [ + { + "expression": "(\"embedding_3072\"::halfvec(3072)) halfvec_cosine_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_binary_hnsw_idx": { + "name": "embedding_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding\")::bit(1536)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_binary_hnsw_idx": { + "name": "embedding_384_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_384\")::bit(384)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_binary_hnsw_idx": { + "name": "embedding_768_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_768\")::bit(768)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_binary_hnsw_idx": { + "name": "embedding_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_1024\")::bit(1024)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_binary_hnsw_idx": { + "name": "embedding_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_3072\")::bit(3072)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "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": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_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_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 + }, + "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_binary_hnsw_idx": { + "name": "embedding_search_binary_hnsw_idx", + "columns": [ + { + "expression": "binary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_384_binary_hnsw_idx": { + "name": "embedding_search_384_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_768_binary_hnsw_idx": { + "name": "embedding_search_768_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_1024_binary_hnsw_idx": { + "name": "embedding_search_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_3072_binary_hnsw_idx": { + "name": "embedding_search_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "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", + "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 + }, + "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", + "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 + } + }, + "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 + }, + "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_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_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 + } + }, + "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')" + } + }, + "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 + } + }, + "indexes": { + "kcsl_connector_started_at_idx": { + "name": "kcsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcsl_started_at_partial_idx": { + "name": "kcsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector_sync_log\".\"status\" = 'started'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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.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 + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_secret_provenance": { + "name": "memory_secret_provenance", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memory_secret_provenance_memory_id_memory_id_fk": { + "name": "memory_secret_provenance_memory_id_memory_id_fk", + "tableFrom": "memory_secret_provenance", + "tableTo": "memory", + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_secret_provenance_status_check": { + "name": "memory_secret_provenance_status_check", + "value": "\"memory_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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_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.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.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_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_period_cost_idx": { + "name": "usage_log_billing_period_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_created_at_cost_idx": { + "name": "usage_log_billing_entity_created_at_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_deadline_idx": { + "name": "workflow_execution_logs_running_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_started_at_idx": { + "name": "workflow_execution_logs_redacting_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'redacting'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_deadline_idx": { + "name": "workflow_execution_logs_redacting_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "mounted_secrets": { + "name": "mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_operation", + "columnsFrom": ["deployment_operation_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_secret_scope": { + "name": "inbox_secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "inbox_mounted_secrets": { + "name": "inbox_mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_inbox_provider_id_idx": { + "name": "workspace_inbox_provider_id_idx", + "columns": [ + { + "expression": "inbox_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_storage_used_bytes_non_negative": { + "name": "workspace_storage_used_bytes_non_negative", + "value": "\"workspace\".\"storage_used_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_collab_state": { + "name": "workspace_file_collab_state", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "doc_state": { + "name": "doc_state", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_collab_state_file_id_workspace_files_id_fk": { + "name": "workspace_file_collab_state_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_collab_state", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_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_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_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_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": "now()" + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_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 + } + }, + "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_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 80a76668d72..3fd48951b0e 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2465,6 +2465,13 @@ "when": 1789588587193, "tag": "0352_search_document_lookup", "breakpoints": true + }, + { + "idx": 353, + "version": "7", + "when": 1789591219081, + "tag": "0353_search_mcp_activity", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index f80a377dc49..335fbd9dc94 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -4674,6 +4674,64 @@ export const organizationSearchInvocation = pgTable( }) ) +/** MCP tool attempts, separate from successful Search invocations and billable usage. */ +export const organizationSearchMcpInvocation = pgTable( + 'organization_search_mcp_invocation', + { + id: text('id').primaryKey(), + organizationId: text('organization_id').notNull(), + userId: text('user_id'), + authKind: text('auth_kind') + .$type<'oauth_access_token' | 'personal_api_key' | 'workspace_api_key'>() + .notNull(), + /** Snapshots survive OAuth client deletion; names are client-declared, not verified branding. */ + oauthClientId: text('oauth_client_id'), + clientName: text('client_name'), + toolName: text('tool_name').$type<'search' | 'read_document' | 'chat'>().notNull(), + outcome: text('outcome').$type<'success' | 'error' | 'cancelled' | 'rate_limited'>().notNull(), + durationMs: integer('duration_ms').notNull(), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + organizationFk: foreignKey({ + name: 'org_search_mcp_invocation_org_fk', + columns: [table.organizationId], + foreignColumns: [organization.id], + }).onDelete('cascade'), + userFk: foreignKey({ + name: 'org_search_mcp_invocation_user_fk', + columns: [table.userId], + foreignColumns: [user.id], + }).onDelete('set null'), + organizationCreatedAtIdx: index('organization_search_mcp_invocation_org_created_idx').on( + table.organizationId, + table.createdAt + ), + userIdIdx: index('organization_search_mcp_invocation_user_idx').on(table.userId), + toolNameCheck: check( + 'organization_search_mcp_invocation_tool_check', + sql`${table.toolName} IN ('search', 'read_document', 'chat')` + ), + outcomeCheck: check( + 'organization_search_mcp_invocation_outcome_check', + sql`${table.outcome} IN ('success', 'error', 'cancelled', 'rate_limited')` + ), + durationBounds: check( + 'organization_search_mcp_invocation_duration_check', + sql`${table.durationMs} >= 0` + ), + clientNameBounds: check( + 'organization_search_mcp_invocation_client_name_check', + sql`length(${table.clientName}) <= 256` + ), + authCheck: check( + 'organization_search_mcp_invocation_auth_check', + sql`(${table.authKind} = 'oauth_access_token' AND ${table.oauthClientId} IS NOT NULL) + OR (${table.authKind} IN ('personal_api_key', 'workspace_api_key') AND ${table.oauthClientId} IS NULL AND ${table.clientName} IS NULL)` + ), + }) +) + export const usageLog = pgTable( 'usage_log', { diff --git a/packages/logger/src/index.test.ts b/packages/logger/src/index.test.ts index 20ac5fa4ea6..e992f70a41d 100644 --- a/packages/logger/src/index.test.ts +++ b/packages/logger/src/index.test.ts @@ -1,5 +1,6 @@ +import { logs } from '@opentelemetry/api-logs' +import { createLogger, Logger, LogLevel, runWithRequestContext, setRequestAuth } from '@sim/logger' import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' -import { createLogger, Logger, LogLevel } from './index' /** * Tests for the console logger module. @@ -247,6 +248,72 @@ describe('Logger', () => { }) }) + describe('request attribution', () => { + test('exports authenticated OAuth attribution to both JSON and OpenTelemetry logs', () => { + const emit = vi.fn() + const getLoggerSpy = vi + .spyOn(logs, 'getLogger') + .mockReturnValue({ emit, enabled: () => true }) + try { + runWithRequestContext( + { + requestId: 'req-oauth', + client: { surface: 'unknown', source: 'unidentified', name: 'http-client' }, + }, + () => { + setRequestAuth({ kind: 'oauth_access_token', clientId: 'registered-client' }) + new Logger('Test', { enabled: true, colorize: false, logLevel: LogLevel.INFO }).info( + 'Tool completed' + ) + } + ) + const attribution = { + requestId: 'req-oauth', + surface: 'api', + clientName: 'http-client', + auth: 'oauth_access_token', + authClientId: 'registered-client', + } + expect(JSON.parse(consoleLogSpy.mock.calls[0][0] as string)).toMatchObject(attribution) + expect(emit).toHaveBeenCalledWith( + expect.objectContaining({ attributes: expect.objectContaining(attribution) }) + ) + } finally { + getLoggerSpy.mockRestore() + } + }) + + test('keeps concurrent callers separate and does not attach OAuth attribution to API keys', async () => { + const logger = new Logger('Test', { enabled: true, colorize: false, logLevel: LogLevel.INFO }) + await Promise.all( + ['client-a', 'client-b'].map((clientId) => + runWithRequestContext({ requestId: clientId }, async () => { + setRequestAuth({ kind: 'oauth_access_token', clientId }) + await Promise.resolve() + logger.info('Tool completed') + }) + ) + ) + runWithRequestContext({ requestId: 'req-key' }, () => { + setRequestAuth({ kind: 'personal_api_key' }) + logger.info('Tool completed') + }) + logger.info('Outside request') + + const records = consoleLogSpy.mock.calls.map((call: unknown[]) => + JSON.parse(call[0] as string) + ) + expect(records.slice(0, 2)).toEqual([ + expect.objectContaining({ requestId: 'client-a', authClientId: 'client-a' }), + expect.objectContaining({ requestId: 'client-b', authClientId: 'client-b' }), + ]) + expect(records[2]).toMatchObject({ requestId: 'req-key', auth: 'personal_api_key' }) + expect(records[2]).not.toHaveProperty('authClientId') + expect(records[3]).not.toHaveProperty('authClientId') + expect(records[3]).not.toHaveProperty('requestId') + }) + }) + describe('structured serialization safety', () => { const createEnabledLogger = () => new Logger('Test', { enabled: true, colorize: false, logLevel: LogLevel.DEBUG }) diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts index a60099b86ba..d13b7f47af0 100644 --- a/packages/logger/src/index.ts +++ b/packages/logger/src/index.ts @@ -324,6 +324,7 @@ const requestContextMetadata = (context: RequestContext): LoggerMetadata => { if (context.auth) { metadata.auth = context.auth.kind if (context.auth.service) metadata.authService = context.auth.service + if (context.auth.clientId) metadata.authClientId = context.auth.clientId } if (context.callChain) metadata.callDepth = context.callChain.length return metadata @@ -419,8 +420,6 @@ export class Logger { private log(level: LogLevel, message: string, ...args: unknown[]) { if (!this.shouldLog(level)) return - emitOtelLogRecord(level, this.module, message, this.metadata, args) - const timestamp = new Date().toISOString() const formattedArgs = this.formatArgs(args) @@ -428,6 +427,7 @@ export class Logger { const effectiveMetadata = reqCtx ? { ...requestContextMetadata(reqCtx), ...this.metadata } : this.metadata + emitOtelLogRecord(level, this.module, message, effectiveMetadata, args) const metadataEntries = Object.entries(filterUndefined(effectiveMetadata)) const metadataStr = metadataEntries.length > 0 diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts index cdd6f68e5ea..e44f7a4b5ab 100644 --- a/packages/testing/src/mocks/schema.mock.ts +++ b/packages/testing/src/mocks/schema.mock.ts @@ -1644,6 +1644,18 @@ export const schemaMock = { approved: 'organizationSearchIntegration.approved', updatedAt: 'organizationSearchIntegration.updatedAt', }, + organizationSearchMcpInvocation: { + id: 'organizationSearchMcpInvocation.id', + organizationId: 'organizationSearchMcpInvocation.organizationId', + userId: 'organizationSearchMcpInvocation.userId', + authKind: 'organizationSearchMcpInvocation.authKind', + oauthClientId: 'organizationSearchMcpInvocation.oauthClientId', + clientName: 'organizationSearchMcpInvocation.clientName', + toolName: 'organizationSearchMcpInvocation.toolName', + outcome: 'organizationSearchMcpInvocation.outcome', + durationMs: 'organizationSearchMcpInvocation.durationMs', + createdAt: 'organizationSearchMcpInvocation.createdAt', + }, organizationSearchInvocation: { id: 'organizationSearchInvocation.id', organizationId: 'organizationSearchInvocation.organizationId', From 42a413b79b9bc0d01764c47d54b388295bfb33a9 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 16 Sep 2026 14:13:29 -0700 Subject: [PATCH 21/43] fix(slack-search): wait for content before starting replies (#7891) * fix(slack-search): wait for content before starting replies * fix(slack-search): settle sessions after failure notification errors * fix(slack-search): allow one authorized status cleanup attempt --- .../lib/slack-search/assistant-stream.test.ts | 426 +++++++++++++++--- apps/sim/lib/slack-search/assistant-stream.ts | 140 +++--- 2 files changed, 453 insertions(+), 113 deletions(-) diff --git a/apps/sim/lib/slack-search/assistant-stream.test.ts b/apps/sim/lib/slack-search/assistant-stream.test.ts index 5a3b53ce65d..0aea1374e2b 100644 --- a/apps/sim/lib/slack-search/assistant-stream.test.ts +++ b/apps/sim/lib/slack-search/assistant-stream.test.ts @@ -36,10 +36,16 @@ beforeEach(() => { api.project.mockImplementation((value: unknown) => ({ safe: true, value })) }) +function deliveredChunks() { + return [ + ...api.start.mock.calls.flatMap((call) => call[2]), + ...api.append.mock.calls.flatMap((call) => call[3]), + ] +} + function deliveredText() { - return api.append.mock.calls - .flatMap((call) => call[3]) - .map((chunk) => chunk.text) + return deliveredChunks() + .flatMap((chunk) => (chunk.type === 'markdown_text' ? [chunk.text] : [])) .join('') } @@ -111,6 +117,301 @@ function toolResult( } } +describe('Slack lazy stream lifecycle', () => { + it('uses native processing status until public content is ready', async () => { + const { stream, controller } = setup() + await stream.start() + expect(api.status).toHaveBeenCalledExactlyOnceWith( + 'test-token', + { channel: 'D1', threadTs: '1.1', initiatorUserId: 'U1' }, + 'processing', + controller.signal + ) + expect(api.start).not.toHaveBeenCalled() + await stream.onEvent({ type: 'text', payload: { channel: 'thinking', text: 'private' } }) + await stream.onEvent({ + type: 'text', + payload: { channel: 'assistant', text: 'private' }, + scope: { lane: 'subagent', agentId: 'child' }, + }) + for (const text of ['', ' \n', 'private', '{"id":"unverified"}' }, + }) + expect(api.start).not.toHaveBeenCalled() + await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: 'Answer.' } }) + await stream.finish(result) + expect(api.start).toHaveBeenCalledOnce() + expect(deliveredText()).toBe(' \nAnswer.') + expect(api.stop).toHaveBeenCalledOnce() + }) + + it('preserves whitespace and starts with the first safe text without waiting for a timer', async () => { + vi.spyOn(Date, 'now').mockReturnValue(1000) + try { + const { stream } = setup() + await stream.start() + for (const text of ['', ' ', '\n', 'Hello']) { + await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text } }) + expect(api.start).not.toHaveBeenCalled() + } + await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: ' ' } }) + expect(api.start).toHaveBeenCalledOnce() + expect(deliveredText()).toBe(' \nHello ') + expect(api.append).not.toHaveBeenCalled() + await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: 'world. ' } }) + expect(api.append).not.toHaveBeenCalled() + vi.mocked(Date.now).mockReturnValue(1750) + await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: 'Next ' } }) + expect(api.append).toHaveBeenCalledOnce() + expect(deliveredText()).toBe(' \nHello world. Next ') + await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: 'line.' } }) + await stream.finish(result) + expect(deliveredText()).toBe(' \nHello world. Next line.') + expect(api.start).toHaveBeenCalledOnce() + } finally { + vi.restoreAllMocks() + } + }) + + it('includes a long whitespace prefix in the same start request as meaningful text', async () => { + const { stream } = setup() + await stream.start() + const text = `${' '.repeat(4001)}Answer. ` + await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text } }) + expect(api.start).toHaveBeenCalledOnce() + expect(api.start.mock.calls[0][2]).toEqual([ + { type: 'markdown_text', text: ' '.repeat(4000) }, + { type: 'markdown_text', text: ' Answer. ' }, + ]) + expect(deliveredText()).toBe(text) + }) + + it('shows tool progress immediately during tool latency, even before any answer text', async () => { + const { stream } = setup() + await stream.start() + await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: '\n' } }) + expect(api.start).not.toHaveBeenCalled() + await stream.onEvent(toolCall()) + expect(api.start).toHaveBeenCalledOnce() + expect(api.start.mock.calls[0][2]).toEqual([ + { type: 'markdown_text', text: '\n' }, + { type: 'markdown_text', text: '\n\n' }, + { + type: 'task_update', + id: expect.any(String), + title: 'Searching documents…', + status: 'in_progress', + }, + ]) + expect(api.append).not.toHaveBeenCalled() + expect(api.stop).not.toHaveBeenCalled() + await stream.onEvent(toolResult()) + await stream.finish(result) + expect(deliveredChunks().at(-1)).toEqual({ + ...api.start.mock.calls[0][2][2], + status: 'complete', + }) + expect(api.stop).toHaveBeenCalledOnce() + }) + + it.each(['', ' \n\t', 'private', 'https://unverified.test '])( + 'settles an answer with no public text without creating a blank reply: %j', + async (text) => { + const { stream, controller } = setup() + await stream.start() + await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text } }) + await stream.finish(result) + await stream.terminateAfterFailure() + expect(api.start).not.toHaveBeenCalled() + expect(api.append).not.toHaveBeenCalled() + expect(api.stop).not.toHaveBeenCalled() + expect(api.status).toHaveBeenCalledTimes(2) + expect(api.status).toHaveBeenLastCalledWith( + 'test-token', + { channel: 'D1', threadTs: '1.1' }, + 'active', + controller.signal + ) + } + ) + + it('rejects content and completion after Stop before the first visible chunk', async () => { + const { stream, controller } = setup() + await stream.start() + controller.abort(new Error('stopped')) + await expect( + stream.onEvent({ + type: 'text', + payload: { channel: 'assistant', text: 'Late answer. ' }, + }) + ).rejects.toThrow('stopped') + await expect(stream.finish(result)).rejects.toThrow('stopped') + expect(api.start).not.toHaveBeenCalled() + expect(api.append).not.toHaveBeenCalled() + expect(api.stop).not.toHaveBeenCalled() + }) + + it.each(['confirmed', 'thrown'])( + 'delivers a %s failure before content once and ends processing', + async (kind) => { + const { stream, controller, beforeCleanup } = setup() + await stream.start() + if (kind === 'thrown') { + controller.abort(new Error('private backend error')) + await stream.terminateAfterFailure() + } else { + await stream.finishWithError() + } + await stream.terminateAfterFailure() + expect(api.start).toHaveBeenCalledOnce() + expect(deliveredText()).toBe('I couldn’t complete this search. Please try again.') + expect(api.append).not.toHaveBeenCalled() + expect(api.stop).toHaveBeenCalledExactlyOnceWith( + 'test-token', + 'D1', + '1.2', + 'active', + expect.any(AbortSignal), + [], + [] + ) + const signal = api.start.mock.calls[0][4] + expect(signal.aborted).toBe(false) + if (kind === 'thrown') { + expect(signal).not.toBe(controller.signal) + expect(beforeCleanup).toHaveBeenCalledExactlyOnceWith(signal) + } + } + ) + + it.each(['confirmed', 'thrown'])( + 'ends processing when a %s failure notification fails without replaying it', + async (kind) => { + const { stream, controller, beforeCleanup } = setup() + await stream.start() + api.start.mockRejectedValueOnce(new Error('failure response lost')) + if (kind === 'thrown') { + controller.abort(new Error('private backend error')) + await expect(stream.terminateAfterFailure()).rejects.toThrow('failure response lost') + } else { + await expect(stream.finishWithError()).rejects.toThrow('failure response lost') + } + expect(api.status).toHaveBeenCalledTimes(2) + expect(api.status).toHaveBeenLastCalledWith( + 'test-token', + { channel: 'D1', threadTs: '1.1' }, + 'active', + api.start.mock.calls[0][4] + ) + if (kind === 'thrown') { + expect(api.start.mock.calls[0][4]).not.toBe(controller.signal) + expect(beforeCleanup).toHaveBeenCalledExactlyOnceWith(api.start.mock.calls[0][4]) + } + await stream.terminateAfterFailure() + expect(api.status).toHaveBeenCalledTimes(2) + expect(api.start).toHaveBeenCalledOnce() + expect(api.append).not.toHaveBeenCalled() + expect(api.stop).not.toHaveBeenCalled() + } + ) + + it('cleans up with fresh authority if the failure notification is aborted before settling', async () => { + const { stream, controller, beforeCleanup } = setup() + await stream.start() + api.start.mockImplementationOnce(async () => { + controller.abort(new Error('deadline exceeded')) + throw controller.signal.reason + }) + await expect(stream.finishWithError()).rejects.toThrow('deadline exceeded') + expect(api.status).toHaveBeenCalledOnce() + await stream.terminateAfterFailure() + await stream.terminateAfterFailure() + expect(api.start).toHaveBeenCalledOnce() + expect(api.stop).not.toHaveBeenCalled() + expect(api.status).toHaveBeenCalledTimes(2) + const cleanupSignal = api.status.mock.calls[1][3] + expect(cleanupSignal).not.toBe(controller.signal) + expect(cleanupSignal.aborted).toBe(false) + expect(beforeCleanup).toHaveBeenCalledExactlyOnceWith(cleanupSignal) + expect(api.status).toHaveBeenLastCalledWith( + 'test-token', + { channel: 'D1', threadTs: '1.1' }, + 'active', + cleanupSignal + ) + }) + + it.each([false, true])( + 'attempts status cleanup once after both notification and status fail (cleanup fails: %s)', + async (cleanupFails) => { + const { stream, controller, beforeCleanup } = setup() + await stream.start() + api.start.mockRejectedValueOnce(new Error('notification response lost')) + api.status.mockRejectedValueOnce(new Error('status response lost')) + await expect(stream.finishWithError()).rejects.toThrow('status response lost') + expect(controller.signal.aborted).toBe(true) + if (cleanupFails) { + api.status.mockRejectedValueOnce(new Error('cleanup response lost')) + await expect(stream.terminateAfterFailure()).rejects.toThrow('cleanup response lost') + } else { + await stream.terminateAfterFailure() + } + await stream.terminateAfterFailure() + expect(api.status).toHaveBeenCalledTimes(3) + const cleanupSignal = api.status.mock.calls[2][3] + expect(cleanupSignal).not.toBe(controller.signal) + expect(cleanupSignal.aborted).toBe(false) + expect(beforeCleanup).toHaveBeenCalledExactlyOnceWith(cleanupSignal) + expect(beforeCleanup.mock.invocationCallOrder[0]).toBeLessThan( + api.status.mock.invocationCallOrder[2] + ) + expect(api.status).toHaveBeenLastCalledWith( + 'test-token', + { channel: 'D1', threadTs: '1.1' }, + 'active', + cleanupSignal + ) + expect(api.start).toHaveBeenCalledOnce() + expect(api.append).not.toHaveBeenCalled() + expect(api.stop).not.toHaveBeenCalled() + } + ) + + it('requires fresh authority before retrying a failed status reset', async () => { + const { stream, beforeCleanup } = setup() + await stream.start() + api.start.mockRejectedValueOnce(new Error('notification response lost')) + api.status.mockRejectedValueOnce(new Error('status response lost')) + await expect(stream.finishWithError()).rejects.toThrow('status response lost') + beforeCleanup.mockRejectedValueOnce(new Error('authority revoked')) + await expect(stream.terminateAfterFailure()).rejects.toThrow('authority revoked') + await stream.terminateAfterFailure() + expect(api.status).toHaveBeenCalledTimes(2) + expect(api.start).toHaveBeenCalledOnce() + expect(api.stop).not.toHaveBeenCalled() + }) + + it('propagates an empty-run status failure and cleans up without posting a reply', async () => { + const { stream, controller } = setup() + await stream.start() + api.status.mockRejectedValueOnce(new Error('status response lost')) + await expect(stream.finish(result)).rejects.toThrow('status response lost') + expect(controller.signal.aborted).toBe(true) + await stream.terminateAfterFailure() + await stream.terminateAfterFailure() + expect(api.status).toHaveBeenCalledTimes(3) + expect(api.start).not.toHaveBeenCalled() + }) +}) + describe('Slack tool progress', () => { it('preserves task positions when secret projection defers delivery until completion', async () => { const { stream, registry } = setup() @@ -133,9 +434,10 @@ describe('Slack tool progress', () => { type: 'text', payload: { channel: 'assistant', text: 'Found a result.' }, }) + expect(api.start).not.toHaveBeenCalled() expect(api.append).not.toHaveBeenCalled() await stream.finish(result) - const chunks = api.append.mock.calls.flatMap((call) => call[3]) + const chunks = deliveredChunks() expect(chunks).toEqual([ { type: 'markdown_text', text: 'Checking [REDACTED_SECRET].\n\n' }, { @@ -147,7 +449,7 @@ describe('Slack tool progress', () => { { type: 'task_update', id: chunks[1].id, title: 'Searching documents…', status: 'complete' }, { type: 'markdown_text', text: 'Found a result.' }, ]) - expect(JSON.stringify(api.append.mock.calls)).not.toContain('private-token') + expect(JSON.stringify(deliveredChunks())).not.toContain('private-token') }) it('withholds tasks and following text until preceding citation evidence arrives', async () => { @@ -165,9 +467,7 @@ describe('Slack tool progress', () => { type: 'text', payload: { channel: 'assistant', text: 'Found a result. ' }, }) - expect(api.append.mock.calls.flatMap((call) => call[3])).toEqual([ - { type: 'markdown_text', text: 'Checking ' }, - ]) + expect(deliveredChunks()).toEqual([{ type: 'markdown_text', text: 'Checking ' }]) const completed = toolResult('search_workspace') await stream.onEvent({ ...completed, @@ -186,7 +486,7 @@ describe('Slack tool progress', () => { }, }, }) - const chunks = api.append.mock.calls.flatMap((call) => call[3]) + const chunks = deliveredChunks() expect(chunks).toEqual([ { type: 'markdown_text', text: 'Checking ' }, { type: 'markdown_text', text: '[Policy]() for details.\n\n' }, @@ -200,7 +500,7 @@ describe('Slack tool progress', () => { { type: 'markdown_text', text: 'Found a result. ' }, ]) await stream.finish(result) - expect(api.append.mock.calls.flatMap((call) => call[3])).toEqual(chunks) + expect(deliveredChunks()).toEqual(chunks) }) it('rejects a tool boundary whose prefix is unsafe in the complete secret projection', async () => { @@ -220,6 +520,7 @@ describe('Slack tool progress', () => { await expect(stream.finish(result)).rejects.toThrow( 'The safe answer changed at a tool boundary' ) + expect(api.start).not.toHaveBeenCalled() expect(api.append).not.toHaveBeenCalled() }) @@ -231,16 +532,17 @@ describe('Slack tool progress', () => { await stream.start() await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: 'Checking.' } }) await stream.onEvent(toolCall('search_workspace')) + expect(api.start).not.toHaveBeenCalled() expect(api.append).not.toHaveBeenCalled() - api.append.mockResolvedValueOnce(undefined).mockRejectedValueOnce(new Error('response lost')) + api.append.mockRejectedValueOnce(new Error('response lost')) await expect(stream.finish(result)).rejects.toThrow('response lost') expect(controller.signal.aborted).toBe(true) await stream.terminateAfterFailure() await stream.terminateAfterFailure() - expect(api.append).toHaveBeenCalledTimes(2) + expect(api.append).toHaveBeenCalledOnce() expect(api.stop).toHaveBeenCalledOnce() expect(api.stop.mock.calls[0][6]).toEqual([ - { ...api.append.mock.calls[1][3][0], status: 'error' }, + { ...api.append.mock.calls[0][3][0], status: 'error' }, ]) }) @@ -258,7 +560,7 @@ describe('Slack tool progress', () => { await stream.onEvent(toolResult('search_workspace')) await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: 'Done.' } }) await stream.finish(result) - const chunks = api.append.mock.calls.flatMap((call) => call[3]) + const chunks = deliveredChunks() expect(chunks.map((chunk) => chunk.type)).toEqual([ 'markdown_text', 'markdown_text', @@ -285,9 +587,7 @@ describe('Slack tool progress', () => { controller.abort(new Error('stopped')) await stream.terminateAfterFailure() expect(api.stop.mock.calls[0][6]).toEqual([]) - expect(api.append.mock.calls.flatMap((call) => call[3])).toEqual([ - { type: 'markdown_text', text: 'Checking ' }, - ]) + expect(deliveredChunks()).toEqual([{ type: 'markdown_text', text: 'Checking ' }]) }) it('flushes a batched sentence before starting tool progress', async () => { @@ -304,7 +604,7 @@ describe('Slack tool progress', () => { payload: { channel: 'assistant', text: 'the connected sources for the handbook.' }, }) await stream.onEvent(toolCall('search_workspace')) - const chunks = api.append.mock.calls.flatMap((call) => call[3]) + const chunks = deliveredChunks() expect(chunks).toEqual([ { type: 'markdown_text', text: "I'll search " }, { @@ -345,36 +645,33 @@ describe('Slack tool progress', () => { }) await stream.finish(result) expect(deliveredText()).toBe("I'll search the connected sources.") - expect( - api.append.mock.calls - .flatMap((call) => call[3]) - .every((chunk) => chunk.type === 'markdown_text') - ).toBe(true) + expect(deliveredChunks().every((chunk) => chunk.type === 'markdown_text')).toBe(true) }) it('serializes concurrent text and tool events without duplicating buffered text', async () => { const { stream } = setup() await stream.start() - let releaseAppend!: () => void - api.append.mockImplementationOnce( + let releaseStart!: (value: { channel: string; ts: string }) => void + api.start.mockImplementationOnce( () => - new Promise((resolve) => { - releaseAppend = resolve + new Promise<{ channel: string; ts: string }>((resolve) => { + releaseStart = resolve }) ) const text = stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: "I'll search the connected sources. " }, }) - await vi.waitFor(() => expect(api.append).toHaveBeenCalledOnce(), { interval: 1 }) + await vi.waitFor(() => expect(api.start).toHaveBeenCalledOnce(), { interval: 1 }) const call = stream.onEvent(toolCall('search_workspace')) const completed = stream.onEvent(toolResult('search_workspace')) const finished = stream.finish(result) - expect(api.append).toHaveBeenCalledOnce() + expect(api.start).toHaveBeenCalledOnce() + expect(api.append).not.toHaveBeenCalled() expect(api.stop).not.toHaveBeenCalled() - releaseAppend() + releaseStart({ channel: 'D1', ts: '1.2' }) await Promise.all([text, call, completed, finished]) - const chunks = api.append.mock.calls.flatMap((call) => call[3]) + const chunks = deliveredChunks() expect(deliveredText()).toBe("I'll search the connected sources. \n\n") expect(chunks.map((chunk) => chunk.type)).toEqual([ 'markdown_text', @@ -396,7 +693,7 @@ describe('Slack tool progress', () => { await stream.onEvent(toolResult(name)) await stream.onEvent(toolResult(name)) await stream.finish(result) - const chunks = api.append.mock.calls.flatMap((call) => call[3]) + const chunks = deliveredChunks() expect(chunks).toEqual([ { type: 'task_update', id: expect.any(String), title, status: 'in_progress' }, { type: 'task_update', id: chunks[0].id, title, status: 'complete' }, @@ -411,7 +708,7 @@ describe('Slack tool progress', () => { await stream.onEvent(toolCall('search_workspace', 'search-2')) await stream.onEvent(toolResult('search_workspace', 'search-2')) await stream.onEvent(toolResult('search_workspace', 'search-1')) - const chunks = api.append.mock.calls.flatMap((call) => call[3]) + const chunks = deliveredChunks() expect(chunks[0].id).not.toBe(chunks[1].id) expect(chunks[2]).toEqual({ ...chunks[1], status: 'complete' }) expect(chunks[3]).toEqual({ ...chunks[0], status: 'complete' }) @@ -433,8 +730,9 @@ describe('Slack tool progress', () => { await stream.onEvent(toolCall('internal_tool')) await stream.onEvent(toolResult()) expect(api.append).not.toHaveBeenCalled() + expect(api.start).not.toHaveBeenCalled() await stream.onEvent(toolCall()) - expect(api.append).toHaveBeenCalledOnce() + expect(api.start).toHaveBeenCalledOnce() }) it('reports failed tools without exposing arguments, account labels, or backend errors', async () => { @@ -454,7 +752,7 @@ describe('Slack tool progress', () => { output: { accountLabel: 'private account' }, }, }) - const chunks = api.append.mock.calls.flatMap((call) => call[3]) + const chunks = deliveredChunks() expect(chunks[1]).toEqual({ ...chunks[0], status: 'error' }) expect(JSON.stringify(chunks)).not.toContain('private') }) @@ -464,14 +762,13 @@ describe('Slack tool progress', () => { await stream.start() await stream.onEvent(toolCall()) await stream.finishWithError() - expect(api.stop.mock.calls[0][6]).toEqual([ - { ...api.append.mock.calls[0][3][0], status: 'error' }, - ]) + expect(api.stop.mock.calls[0][6]).toEqual([{ ...deliveredChunks()[0], status: 'error' }]) }) it('aborts an ambiguous progress send and cleans up once without replaying it', async () => { const { stream, controller } = setup() await stream.start() + await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: 'Checking.\n\n' } }) api.append.mockRejectedValueOnce(new Error('progress response lost')) await expect(stream.onEvent(toolCall())).rejects.toThrow('progress response lost') expect(controller.signal.aborted).toBe(true) @@ -491,11 +788,13 @@ describe('Slack tool progress', () => { beforeDelivery.mockRejectedValueOnce(new Error('authority revoked')) await expect(stream.onEvent(toolCall())).rejects.toThrow('authority revoked') expect(controller.signal.aborted).toBe(true) + expect(api.start).not.toHaveBeenCalled() expect(api.append).not.toHaveBeenCalled() const cancelled = setup() await cancelled.stream.start() cancelled.controller.abort(new Error('stopped')) await expect(cancelled.stream.onEvent(toolCall())).rejects.toThrow('stopped') + expect(api.start).not.toHaveBeenCalled() expect(api.append).not.toHaveBeenCalled() }) }) @@ -551,16 +850,11 @@ describe('Slack Assistant delivery', () => { expect(api.start).toHaveBeenCalledWith( 'test-token', { channel: 'D1', threadTs: '1.1' }, - [], + [{ type: 'markdown_text', text: 'Hello world. ' }], 'timeline', expect.any(AbortSignal) ) - expect( - api.append.mock.calls - .flatMap((call) => call[3]) - .map((chunk) => chunk.text) - .join('') - ).toBe('Hello world. ') + expect(deliveredText()).toBe('Hello world. ') expect(api.stop).toHaveBeenCalledOnce() }) it('aborts after an ambiguous append and closes the known stream without replaying text', async () => { @@ -568,7 +862,10 @@ describe('Slack Assistant delivery', () => { api.append.mockRejectedValueOnce(new Error('connection closed')) await stream.start() await expect( - stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: 'answer ' } }) + stream.onEvent({ + type: 'text', + payload: { channel: 'assistant', text: `${'a'.repeat(4000)} answer ` }, + }) ).rejects.toThrow('connection closed') expect(controller.signal.aborted).toBe(true) await expect(stream.finish(result)).rejects.toThrow('connection closed') @@ -591,13 +888,23 @@ describe('Slack Assistant delivery', () => { it('does not guess a stream identity after an ambiguous start', async () => { const { stream } = setup() api.start.mockRejectedValueOnce(new Error('start response lost')) - await expect(stream.start()).rejects.toThrow('start response lost') + await stream.start() + await expect(stream.onEvent(toolCall())).rejects.toThrow('start response lost') await stream.terminateAfterFailure() + await stream.terminateAfterFailure() + expect(api.start).toHaveBeenCalledOnce() expect(api.stop).not.toHaveBeenCalled() + expect(api.status).toHaveBeenLastCalledWith( + 'test-token', + { channel: 'D1', threadTs: '1.1' }, + 'active', + expect.any(AbortSignal) + ) }) it('does not retry an ambiguous stop during cleanup', async () => { const { stream } = setup() await stream.start() + await stream.onEvent(toolCall()) api.stop.mockRejectedValueOnce(new Error('stop response lost')) await expect(stream.finish(result)).rejects.toThrow('stop response lost') await stream.terminateAfterFailure() @@ -618,6 +925,7 @@ describe('Slack Assistant delivery', () => { controller.abort(new Error('Assistant failed')) beforeCleanup.mockRejectedValueOnce(new Error('authority revoked')) await expect(stream.terminateAfterFailure()).rejects.toThrow('authority revoked') + expect(api.start).not.toHaveBeenCalled() expect(api.stop).not.toHaveBeenCalled() }) it('separates public text before and after a tool call', async () => { @@ -636,12 +944,7 @@ describe('Slack Assistant delivery', () => { }) await stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: 'Found it.' } }) await stream.finish(result) - expect( - api.append.mock.calls - .flatMap((call) => call[3]) - .map((chunk) => chunk.text) - .join('') - ).toBe('Searching.\n\nFound it.') + expect(deliveredText()).toBe('Searching.\n\nFound it.') }) it.each(['options', 'question', 'thinking', 'usage_upgrade', 'credential', 'workspace_resource'])( 'withholds %s payloads across every stream boundary', @@ -656,8 +959,10 @@ describe('Slack Assistant delivery', () => { it('closes a confirmed Assistant failure with a safe error on the existing stream', async () => { const { stream, beforeDelivery } = setup() await stream.start() + await stream.onEvent(toolCall()) + await stream.onEvent(toolResult()) await stream.finishWithError() - expect(beforeDelivery).toHaveBeenCalledTimes(2) + expect(beforeDelivery).toHaveBeenCalledTimes(4) expect(api.stop).toHaveBeenCalledWith( 'test-token', 'D1', @@ -672,7 +977,7 @@ describe('Slack Assistant delivery', () => { ], [] ) - expect(api.append).not.toHaveBeenCalled() + expect(deliveredText()).toBe('') }) it('refuses delivery when installation or member access changes', async () => { const { stream, beforeDelivery } = setup() @@ -681,6 +986,7 @@ describe('Slack Assistant delivery', () => { await expect( stream.onEvent({ type: 'text', payload: { channel: 'assistant', text: 'answer ' } }) ).rejects.toThrow('membership revoked') + expect(api.start).not.toHaveBeenCalled() expect(api.append).not.toHaveBeenCalled() }) it('places cited source names beside the supported text without a source footer', async () => { @@ -935,9 +1241,13 @@ describe('Slack Assistant delivery', () => { }) const link = '[Employee policy]()' expect(deliveredText()).toBe(`${prefix}${link} Done.`) - const chunks = api.append.mock.calls.flatMap((call) => call[3]) - expect(chunks.some((chunk) => chunk.text.includes(link))).toBe(true) - expect(chunks.every((chunk) => chunk.text.length <= 4000)).toBe(true) + const chunks = deliveredChunks() + expect( + chunks.some((chunk) => chunk.type === 'markdown_text' && chunk.text.includes(link)) + ).toBe(true) + expect( + chunks.every((chunk) => chunk.type === 'markdown_text' && chunk.text.length <= 4000) + ).toBe(true) }) it.each([ ['Answer {"id":"x","url":"https://evil.test"} done ', 'Answer '], diff --git a/apps/sim/lib/slack-search/assistant-stream.ts b/apps/sim/lib/slack-search/assistant-stream.ts index c6d85a9d95d..87adf6dcd8e 100644 --- a/apps/sim/lib/slack-search/assistant-stream.ts +++ b/apps/sim/lib/slack-search/assistant-stream.ts @@ -116,12 +116,17 @@ type ToolProgress = Extract /** Serial delivery through the same provider primitives as Slack blocks; ambiguous sends are terminal. */ export class SlackSearchAssistantStream { private stream?: { channel: string; ts: string } + private sessionStarted = false + private streamStartAttempted = false + private leadingChunks: SlackStreamChunk[] = [] private text = '' + /** Safe text already delivered or buffered ahead of the first visible chunk. */ private sent = '' private lastSentAt = 0 private failure?: Error private closed = false private closeAttempted = false + private cleanupAttempted = false private pendingEvents: Promise = Promise.resolve() private evidence = new Map>() private toolProgress = new Map() @@ -151,13 +156,7 @@ export class SlackSearchAssistantStream { 'processing', controller.signal ) - this.stream = await startSlackAgentStream( - token, - { channel, threadTs }, - [], - 'timeline', - controller.signal - ) + this.sessionStarted = true }) } @@ -168,6 +167,7 @@ export class SlackSearchAssistantStream { private async handleEvent(event: StreamEvent) { if (this.failure) throw this.failure + this.options.controller.signal.throwIfAborted() if (event.type === 'tool' && 'phase' in event.payload && event.payload.phase === 'result') { const { toolName, success, status, output } = event.payload this.collectSources([ @@ -192,7 +192,7 @@ export class SlackSearchAssistantStream { if (event.type !== 'text' || event.payload.channel !== 'assistant' || event.scope) return this.text += event.payload.text if (this.text.length > 128_000) throw new Error('Slack answer exceeds the supported size') - if (Date.now() - this.lastSentAt >= 750) await this.flush(false) + if (!this.stream || Date.now() - this.lastSentAt >= 750) await this.flush(false) } /** Only static labels reach Slack; arguments, account details, and backend errors stay private. */ @@ -272,16 +272,9 @@ export class SlackSearchAssistantStream { /** Unresolved citations and partial markup must not let a task overtake withheld text. */ if (!complete && prefix !== this.projectAnswer(preceding, true, sources)) return await this.deliver(async () => { - if (!this.stream || this.closed) throw new Error('Slack stream is not active') /** Include an ambiguously started task in failure cleanup, but never an unsent task. */ if (!this.deliveredProgress.has(chunk.id)) this.deliveredProgress.set(chunk.id, chunk) - await appendSlackAgentStream( - this.options.token, - this.stream.channel, - this.stream.ts, - [chunk], - this.options.controller.signal - ) + await this.writeChunk(chunk, this.options.controller.signal) }) this.deliveredProgress.set(chunk.id, chunk) this.pendingProgress.shift() @@ -299,7 +292,7 @@ export class SlackSearchAssistantStream { private async appendText(text: string) { if (this.sent.startsWith(text)) return if (!text.startsWith(this.sent)) throw new Error('The safe answer changed after delivery') - const { token, controller } = this.options + const { controller } = this.options let pending = text.slice(this.sent.length) while (pending.length) { let end = Math.min(4000, pending.length) @@ -314,19 +307,36 @@ export class SlackSearchAssistantStream { if (end === 0) throw new Error('Slack citation exceeds the supported chunk size') const chunk = pending.slice(0, end) await this.deliver(async () => { - if (!this.stream || this.closed) throw new Error('Slack stream is not active') - await appendSlackAgentStream( - token, - this.stream.channel, - this.stream.ts, - [{ type: 'markdown_text', text: chunk }], - controller.signal - ) + await this.writeChunk({ type: 'markdown_text', text: chunk }, controller.signal) }) this.sent += chunk pending = pending.slice(chunk.length) - this.lastSentAt = Date.now() + if (this.stream) this.lastSentAt = Date.now() + } + } + + /** Start with visible content, preserving buffered whitespace and tool positions in that request. */ + private async writeChunk(chunk: SlackStreamChunk, signal: AbortSignal) { + if (!this.sessionStarted || this.closed) throw new Error('Slack session is not active') + const { token, channel, threadTs } = this.options + if (this.stream) { + await appendSlackAgentStream(token, this.stream.channel, this.stream.ts, [chunk], signal) + return + } + if (this.streamStartAttempted) throw new Error('Slack stream start was not confirmed') + if (chunk.type === 'markdown_text' && !chunk.text.trim()) { + this.leadingChunks.push(chunk) + return } + this.streamStartAttempted = true + this.stream = await startSlackAgentStream( + token, + { channel, threadTs }, + [...this.leadingChunks, chunk], + 'timeline', + signal + ) + this.leadingChunks = [] } async finish(result: OrchestratorResult) { @@ -349,48 +359,68 @@ export class SlackSearchAssistantStream { '\n\nUse the connection buttons in our DM, then reply here when you’re ready to continue.' } await this.flush(true) - await this.close([]) + await this.deliver(() => this.close(false, this.options.controller.signal)) } - /** A confirmed Assistant failure closes the established stream without exposing backend errors. */ + /** A confirmed Assistant failure is visible even if no answer stream has started. */ async finishWithError() { - await this.close(FAILURE_BLOCKS) + await this.pendingEvents + await this.deliver(() => this.close(true, this.options.controller.signal)) } - /** Closes a known stream once after abort, with fresh authority and no replay of failed sends. */ + /** Settle once after abort, with fresh authority and no replay of ambiguous sends. */ async terminateAfterFailure() { - if (!this.stream || this.closed || this.closeAttempted) return + if ( + !this.sessionStarted || + this.closed || + this.cleanupAttempted || + (this.stream && this.closeAttempted) + ) + return + this.cleanupAttempted = true const signal = AbortSignal.timeout(5000) await this.options.beforeCleanup(signal) signal.throwIfAborted() - this.closeAttempted = true - await stopSlackAgentStream( - this.options.token, - this.stream.channel, - this.stream.ts, - 'active', - signal, - FAILURE_BLOCKS, - this.interruptedToolProgress() - ) - this.closed = true + if (this.closeAttempted) { + /** Only the idempotent status reset can repeat; never replay an unconfirmed message send. */ + const { token, channel, threadTs } = this.options + await setSlackAgentSessionStatus(token, { channel, threadTs }, 'active', signal) + this.closed = true + return + } + await this.close(true, signal) } - private async close(blocks: Record[]) { - await this.deliver(async () => { - if (!this.stream || this.closed) throw new Error('Slack stream is not active') + private async close(failed: boolean, signal: AbortSignal) { + if (!this.sessionStarted || this.closed || this.closeAttempted) + throw new Error('Slack session is not active') + const { token, channel, threadTs } = this.options + let blocks = failed ? FAILURE_BLOCKS : [] + try { + if (!this.stream && failed && !this.streamStartAttempted) { + await this.writeChunk({ type: 'markdown_text', text: SLACK_SEARCH_FAILED_ANSWER }, signal) + blocks = [] + } + } finally { + /** A failed notification must still settle the session, including during failure cleanup. */ + signal.throwIfAborted() this.closeAttempted = true - await stopSlackAgentStream( - this.options.token, - this.stream.channel, - this.stream.ts, - 'active', - this.options.controller.signal, - blocks, - this.interruptedToolProgress() - ) + if (this.stream) { + await stopSlackAgentStream( + token, + this.stream.channel, + this.stream.ts, + 'active', + signal, + blocks, + this.interruptedToolProgress() + ) + } else { + /** Empty runs and unconfirmed starts still need to end the native loading state. */ + await setSlackAgentSessionStatus(token, { channel, threadTs }, 'active', signal) + } this.closed = true - }) + } } assertHealthy() { From bfa86a53a021e662ec03379fca4ce4cada3603e2 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 16 Sep 2026 14:24:40 -0700 Subject: [PATCH 22/43] fix(browser): recover stalled screenshots and preserve image coordinates (#7889) * fix(browser): recover stalled screenshots and preserve image geometry * chore(browser): synchronize screenshot fixture repaint * fix(browser): align coordinate guidance with screenshot mapping * chore(browser): record shared formatter module graph The shared formatter adds one dependency-free module to the home and chat page graphs (3130 on staging, 3131 here). Regenerate those two baseline entries without changing audit tolerances or unrelated entries. --- apps/desktop/e2e/browser-tools.spec.ts | 191 +++++++++++- .../src/main/browser-agent/cdp.test.ts | 289 ++++++++++++++++-- apps/desktop/src/main/browser-agent/cdp.ts | 87 +++++- .../src/main/browser-agent/driver.test.ts | 41 ++- apps/desktop/src/main/browser-agent/driver.ts | 9 +- apps/desktop/src/test/electron-mock.ts | 2 + .../lib/copilot/generated/tool-catalog-v1.ts | 6 +- .../lib/copilot/generated/tool-schemas-v1.ts | 6 +- .../client/browser-tool-execution.test.ts | 10 +- .../tools/client/browser-tool-execution.ts | 56 +--- .../tools/client/browser-tool-result.test.ts | 91 ++++++ .../tools/client/browser-tool-result.ts | 81 +++++ ...check-tool-registry-boundary.baseline.json | 22 +- 13 files changed, 773 insertions(+), 118 deletions(-) create mode 100644 apps/sim/lib/copilot/tools/client/browser-tool-result.test.ts create mode 100644 apps/sim/lib/copilot/tools/client/browser-tool-result.ts diff --git a/apps/desktop/e2e/browser-tools.spec.ts b/apps/desktop/e2e/browser-tools.spec.ts index fa12465e190..2f4eaf9b148 100644 --- a/apps/desktop/e2e/browser-tools.spec.ts +++ b/apps/desktop/e2e/browser-tools.spec.ts @@ -96,7 +96,7 @@ test.describe('browser tools', () => { test.beforeEach(async () => { app = await electron.launch({ - args: ['.'], + args: [process.env.SIM_DESKTOP_E2E_MAIN ?? '.'], cwd: DESKTOP_DIR, env: { ...process.env, @@ -105,9 +105,15 @@ test.describe('browser tools', () => { }, }) window = await app.firstWindow() - await app.evaluate(({ BrowserWindow }) => - BrowserWindow.getAllWindows()[0].webContents.setBackgroundThrottling(false) - ) + await app.evaluate(({ app, BrowserWindow }) => { + const host = BrowserWindow.getAllWindows()[0] + host.webContents.setBackgroundThrottling(false) + app.focus({ steal: true }) + host.focus() + }) + await expect + .poll(() => app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].isFocused())) + .toBe(true) await expect(window.getByRole('heading')).toHaveText('Browser tools fixture') await window.evaluate(async (scope) => { const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop @@ -303,6 +309,180 @@ test.describe('browser tools', () => { }) } + test('recovers a permanently pending native capture without losing the page', async () => { + await openForm() + const before = await app.evaluate(async ({ webContents }, origin) => { + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL() === `${origin}/form`) + if (!contents) throw new Error('Missing capture fixture') + await contents.executeJavaScript(` + document.getElementById('name').value = 'Unsaved work'; + document.getElementById('name').focus(); + `) + contents.capturePage = () => new Promise(() => {}) + return { id: contents.id, url: contents.getURL() } + }, origin) + for (const [color, dominantChannel] of [ + ['rgb(240, 20, 30)', 0], + ['rgb(30, 40, 230)', 2], + ['rgb(20, 220, 50)', 1], + ] as const) { + await app.evaluate( + async ({ webContents }, { id, color }) => { + const contents = webContents.fromId(id) + if (!contents) throw new Error('Capture fixture was replaced') + await contents.executeJavaScript(` + document.body.style.background = ${JSON.stringify(color)}; + new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))) + `) + }, + { id: before.id, color } + ) + const response = await execute('browser_screenshot', {}) + expect(response.ok, response.error).toBe(true) + const shot = response.result as { dataUrl: string } + const pixel = await app.evaluate(({ nativeImage }, dataUrl) => { + const bitmap = nativeImage.createFromDataURL(dataUrl).toBitmap() + return [bitmap[2], bitmap[1], bitmap[0]] + }, shot.dataUrl) + expect(pixel[dominantChannel]).toBeGreaterThan(180) + for (let channel = 0; channel < 3; channel++) { + if (channel !== dominantChannel) + expect(pixel[dominantChannel] - pixel[channel]).toBeGreaterThan(80) + } + } + const after = await app.evaluate(async ({ webContents }, id) => { + const contents = webContents.fromId(id) + if (!contents) throw new Error('Capture fixture was replaced') + return { + id: contents.id, + url: contents.getURL(), + page: await contents.executeJavaScript( + `({value:document.getElementById('name').value,focus:document.activeElement.id})` + ), + } + }, before.id) + expect(after).toEqual({ ...before, page: { value: 'Unsaved work', focus: 'name' } }) + }) + + test('maps a fractional narrow crop back to its actual viewport position', async () => { + await openForm() + const target = await app.evaluate(async ({ webContents }, origin) => { + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL() === `${origin}/form`) + if (!contents) throw new Error('Missing crop fixture') + return contents.executeJavaScript(` + const button = document.createElement('button'); + button.textContent = 'Narrow target'; + button.style.cssText = 'position:absolute;left:20.1px;top:60.1px;width:1.1px;height:100px;padding:0;border:0;overflow:hidden'; + button.onclick = () => { document.body.dataset.cropClicks = Number(document.body.dataset.cropClicks || 0) + 1 }; + document.body.append(button); + const rect = button.getBoundingClientRect(); + ({x:rect.x,y:rect.y,width:rect.width,height:rect.height,devicePixelRatio}); + `) as Promise<{ + x: number + y: number + width: number + height: number + devicePixelRatio: number + }> + }, origin) + const snapshot = await execute('browser_snapshot', {}) + expect(snapshot.ok, snapshot.error).toBe(true) + const line = (snapshot.result as { outline: string }).outline + .split('\n') + .find((line) => line.includes('"Narrow target"')) + const match = line?.match(/\[ref=(\d+)\]/) + if (!match) throw new Error('Missing narrow target reference') + const response = await execute('browser_screenshot', { elementId: Number(match[1]) }) + expect(response.ok, response.error).toBe(true) + const shot = response.result as { + imageSize: { width: number; height: number } + clip: { x: number; y: number; width: number; height: number } + scale: number + } + expect(shot.clip.x).toBeLessThanOrEqual(target.x) + expect(shot.clip.y).toBeLessThanOrEqual(target.y) + expect(shot.clip.x + shot.clip.width).toBeGreaterThanOrEqual(target.x + target.width) + expect(shot.clip.y + shot.clip.height).toBeGreaterThanOrEqual(target.y + target.height) + expect(target.x - shot.clip.x).toBeLessThan(1 / target.devicePixelRatio) + expect(target.y - shot.clip.y).toBeLessThan(1 / target.devicePixelRatio) + expect(shot.scale).toBeCloseTo(shot.imageSize.width / shot.clip.width) + const clicked = await execute('browser_click_at', { + x: shot.clip.x + shot.clip.width / 2, + y: shot.clip.y + shot.clip.height / 2, + }) + expect(clicked.ok, clicked.error).toBe(true) + const count = await app.evaluate(async ({ webContents }, origin) => { + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL() === `${origin}/form`) + return contents?.executeJavaScript('document.body.dataset.cropClicks') + }, origin) + expect(count).toBe('1') + }) + + for (const mode of ['hidden', 'minimized']) { + test(`recovers a stalled capture after restoring a ${mode} window`, async () => { + test.skip(mode === 'minimized' && process.platform !== 'darwin', 'Requires minimize events') + await openForm() + await app.evaluate( + async ({ BrowserWindow, webContents }, { origin, mode }) => { + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL() === `${origin}/form`) + if (!contents) throw new Error('Missing capture fixture') + contents.capturePage = () => new Promise(() => {}) + await contents.executeJavaScript("document.getElementById('name').value = 'Unsaved work'") + const win = BrowserWindow.getAllWindows()[0] + win.blur() + if (mode === 'hidden') win.hide() + else { + const minimized = new Promise((resolve) => win.once('minimize', resolve)) + win.minimize() + await minimized + } + }, + { origin, mode } + ) + const state = () => + app.evaluate(async ({ BrowserWindow, webContents }, origin) => { + const win = BrowserWindow.getAllWindows()[0] + const contents = webContents + .getAllWebContents() + .find((wc) => wc.getURL() === `${origin}/form`) + if (!contents) throw new Error('Missing capture fixture') + return { + id: contents.id, + visible: win.isVisible(), + minimized: win.isMinimized(), + focused: BrowserWindow.getFocusedWindow()?.id ?? null, + bounds: win.getBounds(), + value: await contents.executeJavaScript("document.getElementById('name').value"), + } + }, origin) + const before = await state() + const start = Date.now() + const hiddenCapture = await execute('browser_screenshot', {}) + expect(Date.now() - start).toBeLessThan(12_000) + if (!hiddenCapture.ok) + expect(hiddenCapture.error).toContain('Screenshot frame capture timed out') + expect(await state()).toEqual(before) + await app.evaluate(({ BrowserWindow }, mode) => { + const win = BrowserWindow.getAllWindows()[0] + if (mode === 'minimized') win.restore() + else win.showInactive() + }, mode) + for (let attempt = 0; attempt < 2; attempt++) { + const response = await execute('browser_screenshot', {}) + expect(response.ok, response.error).toBe(true) + } + expect(await state()).toMatchObject({ id: before.id, value: 'Unsaved work' }) + }) + } + for (const mode of ['visible', 'hidden', 'minimized']) { test(`captures a ${mode} window without changing its state`, async () => { test.skip( @@ -383,7 +563,8 @@ test.describe('browser tools', () => { .find((wc) => wc.getURL() === `${origin}/form`) if (!contents) throw new Error('Missing screenshot fixture') await contents.executeJavaScript( - `document.body.style.background = ${JSON.stringify(color)}; void 0` + `document.body.style.background = ${JSON.stringify(color)}; + new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))` ) }, { origin, color } diff --git a/apps/desktop/src/main/browser-agent/cdp.test.ts b/apps/desktop/src/main/browser-agent/cdp.test.ts index 9f7b32b6071..fdc0884e3f7 100644 --- a/apps/desktop/src/main/browser-agent/cdp.test.ts +++ b/apps/desktop/src/main/browser-agent/cdp.test.ts @@ -1,8 +1,14 @@ -import { describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('electron', () => import('@/test/electron-mock')) -import { type nativeImage, WebContentsView, type WebFrameMain } from 'electron' +import { + type NativeImage, + type nativeImage, + type WebContents, + WebContentsView, + type WebFrameMain, +} from 'electron' import { captureScreenshot, clickAt, @@ -491,7 +497,10 @@ describe('browser-agent CDP theme', () => { * snapping back. Resolution is bounded on the returned image instead. */ describe('browser-agent screenshot capture', () => { - function captureFixture(imageSize: { width: number; height: number } | null) { + function captureFixture( + imageSize: { width: number; height: number } | null, + imageContent = 'sim' + ) { const contents = new WebContentsView().webContents vi.mocked(contents.debugger.sendCommand).mockImplementation((method: string) => { if (method === 'Page.getLayoutMetrics') { @@ -513,7 +522,7 @@ describe('browser-agent screenshot capture', () => { getSize: vi.fn(() => imageSize ?? { width: 0, height: 0 }), crop: vi.fn(() => cropped), resize: vi.fn(() => resized), - toJPEG: vi.fn(() => Buffer.from('sim')), + toJPEG: vi.fn(() => Buffer.from(imageContent)), } as unknown as ReturnType vi.mocked(contents.capturePage).mockResolvedValue(image) return { contents, resized, cropped, image } @@ -548,9 +557,54 @@ describe('browser-agent screenshot capture', () => { scale: 2, viewport: { width: 2048, height: 1024 }, imageSize: { width: 400, height: 200 }, + clip: { x: 100, y: 50, width: 200, height: 100 }, }) }) + it('reports the actual CSS crop after rounding a narrow fractional element to pixels', async () => { + const { contents, cropped, image } = captureFixture({ width: 4096, height: 2048 }) + cropped.getSize.mockReturnValue({ width: 3, height: 201 }) + + const shot = await captureScreenshot(contents, { x: 0.1, y: 0.2, width: 1.1, height: 100 }) + + expect(image.crop).toHaveBeenCalledWith({ x: 0, y: 0, width: 3, height: 201 }) + expect(shot).toMatchObject({ + clip: { x: 0, y: 0, width: 1.5, height: 100.5 }, + imageSize: { width: 3, height: 201 }, + scale: 2, + }) + expect(100 / shot.scale).toBe(50) + expect(cropped.resize).not.toHaveBeenCalled() + }) + + it.each([ + { + requested: { x: -10, y: -20, width: 30, height: 40 }, + crop: { x: 0, y: 0, width: 40, height: 40 }, + captured: { x: 0, y: 0, width: 20, height: 20 }, + }, + { + requested: { x: 2040, y: 1020, width: 30, height: 40 }, + crop: { x: 4080, y: 2040, width: 16, height: 8 }, + captured: { x: 2040, y: 1020, width: 8, height: 4 }, + }, + ])( + 'reports only the encoded portion of a crop clamped to the viewport: $requested', + async ({ requested, crop, captured }) => { + const { contents, cropped, image } = captureFixture({ width: 4096, height: 2048 }) + cropped.getSize.mockReturnValue({ width: crop.width, height: crop.height }) + + const shot = await captureScreenshot(contents, requested) + + expect(image.crop).toHaveBeenCalledWith(crop) + expect(shot).toMatchObject({ + clip: captured, + imageSize: { width: crop.width, height: crop.height }, + scale: 2, + }) + } + ) + /** * A 2048px CSS viewport bounded to 1024px is scale 0.5, and the capture * arrives at device resolution (4096px on a 2x display). The resize is what @@ -591,34 +645,217 @@ describe('browser-agent screenshot capture', () => { await expect(captureScreenshot(contents)).rejects.toThrow('empty image') }) - it('bounds a stalled capture and prevents overlapping native surface copies', async () => { - vi.useFakeTimers() - try { + describe('stalled native capture recovery', () => { + beforeEach(() => vi.useFakeTimers()) + afterEach(() => vi.useRealTimers()) + + function observeFrames(contents: WebContents) { + const frames: Array<(image: NativeImage) => void> = [] + vi.mocked(contents.beginFrameSubscription).mockImplementation((...args: unknown[]) => { + const callback = args.at(-1) as (image: NativeImage) => void + frames.push((image) => callback(image)) + }) + return frames + } + + it('recovers repeatedly with fresh frames without overlapping native surface copies', async () => { + const { contents } = captureFixture({ width: 1024, height: 512 }) + vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) + const frames = observeFrames(contents) + + for (let index = 0; index < 5; index++) { + const { image } = captureFixture({ width: 1024, height: 512 }, `frame-${index}`) + const capture = captureScreenshot(contents) + await vi.advanceTimersByTimeAsync(index === 0 ? 5_000 : 0) + expect(contents.capturePage).toHaveBeenCalledOnce() + expect(contents.beginFrameSubscription).toHaveBeenLastCalledWith( + false, + expect.any(Function) + ) + expect(frames).toHaveLength(index + 1) + frames[index](image) + await expect(capture).resolves.toMatchObject({ + dataUrl: `data:image/jpeg;base64,${Buffer.from(`frame-${index}`).toString('base64')}`, + imageSize: { width: 1024, height: 512 }, + }) + expect(contents.endFrameSubscription).toHaveBeenCalledTimes(index + 1) + expect(vi.getTimerCount()).toBe(0) + } + const registered = vi + .mocked(contents.once) + .mock.calls.filter(([event]) => String(event) === 'destroyed') + for (const [, listener] of registered) { + expect(contents.removeListener).toHaveBeenCalledWith('destroyed', listener) + } + expect(contents.reload).not.toHaveBeenCalled() + expect(contents.loadURL).not.toHaveBeenCalled() + }) + + it('bounds both waits and allows another frame attempt after a timeout', async () => { const { contents, image } = captureFixture({ width: 1024, height: 512 }) - let release: (captured: typeof image) => void = () => {} - vi.mocked(contents.capturePage).mockImplementationOnce( - () => - new Promise((resolve) => { - release = resolve - }) - ) - const failed = expect(captureScreenshot(contents)).rejects.toThrow('pixel capture timed out') - await vi.advanceTimersByTimeAsync(5_000) + vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) + const frames = observeFrames(contents) + const failed = expect(captureScreenshot(contents)).rejects.toThrow('frame capture timed out') + await vi.advanceTimersByTimeAsync(9_999) + expect(contents.endFrameSubscription).not.toHaveBeenCalled() + await vi.advanceTimersByTimeAsync(1) await failed + expect(contents.endFrameSubscription).toHaveBeenCalledOnce() expect(vi.getTimerCount()).toBe(0) - await expect(captureScreenshot(contents)).rejects.toThrow( - 'previous screenshot capture is still pending' - ) + + const recovered = captureScreenshot(contents) + await vi.advanceTimersByTimeAsync(0) expect(contents.capturePage).toHaveBeenCalledOnce() - release(image) - await Promise.resolve() - await expect(captureScreenshot(contents)).resolves.toMatchObject({ + frames[1](image) + await expect(recovered).resolves.toMatchObject({ imageSize: { width: 1024, height: 512 } }) + expect(contents.endFrameSubscription).toHaveBeenCalledTimes(2) + expect(vi.getTimerCount()).toBe(0) + }) + + it('ignores a timed-out frame callback while a later subscription is active', async () => { + const { contents, image } = captureFixture({ width: 1024, height: 512 }, 'fresh') + const stale = captureFixture({ width: 1024, height: 512 }, 'stale').image + vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) + const frames = observeFrames(contents) + const failed = expect(captureScreenshot(contents)).rejects.toThrow('frame capture timed out') + await vi.advanceTimersByTimeAsync(10_000) + await failed + + const recovered = captureScreenshot(contents) + const settled = vi.fn() + void recovered.then(settled) + await vi.advanceTimersByTimeAsync(0) + frames[0](stale) + await vi.advanceTimersByTimeAsync(0) + expect(settled).not.toHaveBeenCalled() + expect(contents.endFrameSubscription).toHaveBeenCalledOnce() + frames[1](image) + await expect(recovered).resolves.toMatchObject({ + dataUrl: `data:image/jpeg;base64,${Buffer.from('fresh').toString('base64')}`, + }) + expect(contents.endFrameSubscription).toHaveBeenCalledTimes(2) + }) + + it.each(['resolve', 'reject'] as const)( + 'ignores a late native %s and resumes native captures afterward', + async (outcome) => { + const { contents, image } = captureFixture({ width: 1024, height: 512 }, 'current') + const stale = captureFixture({ width: 1024, height: 512 }, 'stale').image + let settleNative: () => void = () => {} + vi.mocked(contents.capturePage).mockImplementationOnce( + () => + new Promise((resolve, reject) => { + settleNative = () => + outcome === 'resolve' ? resolve(stale) : reject(new Error('late failure')) + }) + ) + const frames = observeFrames(contents) + const capture = captureScreenshot(contents) + const settled = vi.fn() + void capture.then(settled) + await vi.advanceTimersByTimeAsync(5_000) + settleNative() + await vi.advanceTimersByTimeAsync(0) + expect(settled).not.toHaveBeenCalled() + expect(contents.endFrameSubscription).not.toHaveBeenCalled() + frames[0](image) + await expect(capture).resolves.toMatchObject({ + dataUrl: `data:image/jpeg;base64,${Buffer.from('current').toString('base64')}`, + }) + await expect(captureScreenshot(contents)).resolves.toMatchObject({ + dataUrl: `data:image/jpeg;base64,${Buffer.from('current').toString('base64')}`, + }) + expect(contents.capturePage).toHaveBeenCalledTimes(2) + expect(contents.beginFrameSubscription).toHaveBeenCalledOnce() + expect(vi.getTimerCount()).toBe(0) + } + ) + + it('rejects concurrent captures without replacing the active subscription or blocking another tab', async () => { + const { contents, image } = captureFixture({ width: 1024, height: 512 }) + const other = captureFixture({ width: 1024, height: 512 }) + vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) + const frames = observeFrames(contents) + const capture = captureScreenshot(contents) + await vi.advanceTimersByTimeAsync(0) + await expect(captureScreenshot(contents)).rejects.toThrow('already in progress') + expect(contents.capturePage).toHaveBeenCalledOnce() + await vi.advanceTimersByTimeAsync(5_000) + await expect(captureScreenshot(contents)).rejects.toThrow('already in progress') + expect(contents.beginFrameSubscription).toHaveBeenCalledOnce() + expect(contents.endFrameSubscription).not.toHaveBeenCalled() + await expect(captureScreenshot(other.contents)).resolves.toMatchObject({ imageSize: { width: 1024, height: 512 }, }) - expect(contents.capturePage).toHaveBeenCalledTimes(2) - } finally { - vi.useRealTimers() - } + frames[0](image) + await capture + expect(contents.endFrameSubscription).toHaveBeenCalledOnce() + }) + + it.each(['cancel', 'destroy'] as const)( + 'releases frame resources on %s and ignores a subsequent frame', + async (reason) => { + const { contents, image } = captureFixture({ width: 1024, height: 512 }) + vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) + const frames = observeFrames(contents) + const controller = new AbortController() + const removeAbort = vi.spyOn(controller.signal, 'removeEventListener') + const failed = expect( + captureScreenshot(contents, undefined, controller.signal) + ).rejects.toThrow(reason === 'cancel' ? 'cancelled' : 'tab was closed') + await vi.advanceTimersByTimeAsync(5_000) + const destroyed = vi + .mocked(contents.once) + .mock.calls.filter(([event]) => String(event) === 'destroyed') + .at(-1)?.[1] as unknown as (() => void) | undefined + expect(destroyed).toBeDefined() + if (reason === 'cancel') controller.abort() + else { + vi.mocked(contents.isDestroyed).mockReturnValue(true) + destroyed?.() + } + await failed + expect(contents.removeListener).toHaveBeenCalledWith('destroyed', destroyed) + expect(removeAbort).toHaveBeenCalledTimes(2) + expect(contents.endFrameSubscription).toHaveBeenCalledTimes(reason === 'cancel' ? 1 : 0) + frames[0](image) + await vi.advanceTimersByTimeAsync(0) + expect(contents.endFrameSubscription).toHaveBeenCalledTimes(reason === 'cancel' ? 1 : 0) + expect(vi.getTimerCount()).toBe(0) + if (reason === 'cancel') { + const recovered = captureScreenshot(contents) + await vi.advanceTimersByTimeAsync(0) + frames[1](image) + await recovered + expect(contents.capturePage).toHaveBeenCalledOnce() + expect(contents.endFrameSubscription).toHaveBeenCalledTimes(2) + } + } + ) + + it.each(['beginFrameSubscription', 'invalidate'] as const)( + 'cleans up a synchronous %s failure and permits another frame attempt', + async (method) => { + const { contents, image } = captureFixture({ width: 1024, height: 512 }) + vi.mocked(contents.capturePage).mockReturnValue(new Promise(() => {})) + const frames = observeFrames(contents) + vi.mocked(contents[method]).mockImplementationOnce(() => { + throw new Error('frame setup failed') + }) + const failed = expect(captureScreenshot(contents)).rejects.toThrow('frame setup failed') + await vi.advanceTimersByTimeAsync(5_000) + await failed + expect(contents.endFrameSubscription).toHaveBeenCalledOnce() + expect(vi.getTimerCount()).toBe(0) + + const recovered = captureScreenshot(contents) + await vi.advanceTimersByTimeAsync(0) + frames.at(-1)?.(image) + await expect(recovered).resolves.toMatchObject({ imageSize: { width: 1024, height: 512 } }) + expect(contents.capturePage).toHaveBeenCalledOnce() + expect(contents.endFrameSubscription).toHaveBeenCalledTimes(2) + } + ) }) it.each(['cancel', 'destroy'] as const)( diff --git a/apps/desktop/src/main/browser-agent/cdp.ts b/apps/desktop/src/main/browser-agent/cdp.ts index 7c30dc6bd07..cef27dbd389 100644 --- a/apps/desktop/src/main/browser-agent/cdp.ts +++ b/apps/desktop/src/main/browser-agent/cdp.ts @@ -374,6 +374,9 @@ const UNSCALED_SCREENSHOT_QUALITY = 90 const SCREENSHOT_CAPTURE_TIMEOUT_MS = 5_000 /** Native surface copies cannot be cancelled; never accumulate them on a stalled tab. */ const pendingScreenshotCaptures = new WeakSet() +const activeScreenshotCaptures = new WeakSet() + +class ScreenshotCaptureTimeoutError extends Error {} interface CdpViewport { clientWidth: number @@ -398,6 +401,7 @@ export interface ScreenshotCapture { scale: number viewport: ScreenshotSize | null imageSize: ScreenshotSize + clip?: ScreenshotClip } export interface ScreenshotClip { @@ -452,16 +456,12 @@ function sameScreenshotViewport( ) } -/** Captures pixels without changing viewport geometry or exposing a hidden window. */ -async function captureViewportImage( +async function captureNativeViewportImage( contents: WebContents, signal?: AbortSignal ): Promise { signal?.throwIfAborted() if (contents.isDestroyed()) throw new Error('The screenshot tab was closed') - if (pendingScreenshotCaptures.has(contents)) { - throw new Error('A previous screenshot capture is still pending on this tab') - } pendingScreenshotCaptures.add(contents) let timer: ReturnType | undefined let onAbort = () => {} @@ -473,7 +473,10 @@ async function captureViewportImage( signal?.addEventListener('abort', onAbort, { once: true }) contents.once('destroyed', onDestroyed) timer = setTimeout( - () => reject(new Error('Screenshot pixel capture timed out after 5 seconds')), + () => + reject( + new ScreenshotCaptureTimeoutError('Screenshot pixel capture timed out after 5 seconds') + ), SCREENSHOT_CAPTURE_TIMEOUT_MS ) }) @@ -492,6 +495,64 @@ async function captureViewportImage( } } +/** Observes one complete frame; unlike a native surface copy, this wait can be cancelled. */ +async function captureViewportFrame( + contents: WebContents, + signal?: AbortSignal +): Promise { + signal?.throwIfAborted() + if (contents.isDestroyed()) throw new Error('The screenshot tab was closed') + let timer: ReturnType | undefined + let onAbort = () => {} + let onDestroyed = () => {} + let subscribed = false + try { + return await new Promise((resolve, reject) => { + onAbort = () => reject(new Error('Screenshot capture was cancelled')) + onDestroyed = () => reject(new Error('The screenshot tab was closed')) + signal?.addEventListener('abort', onAbort, { once: true }) + contents.once('destroyed', onDestroyed) + timer = setTimeout( + () => reject(new Error('Screenshot frame capture timed out after 5 seconds')), + SCREENSHOT_CAPTURE_TIMEOUT_MS + ) + subscribed = true + contents.beginFrameSubscription(false, (image) => resolve(image)) + contents.invalidate() + }) + } finally { + clearTimeout(timer) + signal?.removeEventListener('abort', onAbort) + contents.removeListener('destroyed', onDestroyed) + if (subscribed && !contents.isDestroyed()) contents.endFrameSubscription() + } +} + +/** Captures pixels without reloading the page, changing geometry, or exposing a hidden window. */ +async function captureViewportImage( + contents: WebContents, + signal?: AbortSignal +): Promise { + signal?.throwIfAborted() + if (contents.isDestroyed()) throw new Error('The screenshot tab was closed') + if (activeScreenshotCaptures.has(contents)) { + throw new Error('A screenshot capture is already in progress on this tab') + } + activeScreenshotCaptures.add(contents) + try { + if (!pendingScreenshotCaptures.has(contents)) { + try { + return await captureNativeViewportImage(contents, signal) + } catch (error) { + if (!(error instanceof ScreenshotCaptureTimeoutError)) throw error + } + } + return await captureViewportFrame(contents, signal) + } finally { + activeScreenshotCaptures.delete(contents) + } +} + /** * Native viewport capture, bounded in time and resolution. * @@ -504,8 +565,9 @@ async function captureViewportImage( * snapshot capture refuses to scale a visible surface for the same reason. * * Bounding resolution therefore happens here instead, on the returned image. - * Optional element crops also happen in memory. Convert output coordinates - * with cssX = (clip?.x ?? 0) + imageX / scale, and the equivalent Y formula. + * Optional element crops also happen in memory. The returned clip records the + * rounded/clamped CSS bounds. Map each image axis using those bounds and the + * returned imageSize, since resizing can round the two dimensions differently. */ export async function captureScreenshot( contents: WebContents, @@ -564,6 +626,12 @@ export async function captureScreenshot( if (croppedSize.width === 0 || croppedSize.height === 0) { throw new Error('The requested screenshot element produced an empty crop') } + const capturedClip = { + x: cropX / xScale, + y: cropY / yScale, + width: croppedSize.width / xScale, + height: croppedSize.height / yScale, + } const cropScale = Math.min( 1, MAX_SCREENSHOT_EDGE / Math.max(croppedSize.width, croppedSize.height) @@ -579,9 +647,10 @@ export async function captureScreenshot( const outputSize = output.getSize() return { dataUrl: `data:image/jpeg;base64,${output.toJPEG(SCREENSHOT_QUALITY).toString('base64')}`, - scale: outputSize.width / clip.width, + scale: outputSize.width / capturedClip.width, viewport: cssViewport, imageSize: outputSize, + clip: capturedClip, } } if (size.width === targetWidth && size.height === targetHeight) { diff --git a/apps/desktop/src/main/browser-agent/driver.test.ts b/apps/desktop/src/main/browser-agent/driver.test.ts index 285112fb61c..94ef24f1bba 100644 --- a/apps/desktop/src/main/browser-agent/driver.test.ts +++ b/apps/desktop/src/main/browser-agent/driver.test.ts @@ -3507,7 +3507,7 @@ describe('credential protection', () => { const result = await driver.executeTool('chat-test', 'browser_click_at', { x: 9999, y: 5 }) expect(result.ok).toBe(false) - expect(result.error).toMatch(/divide image pixels by its scale/) + expect(result.error).toMatch(/X\/Y coordinate mapping and crop origin/) }) it('inserts text into the focused editable at the caret', async () => { @@ -4121,6 +4121,44 @@ describe('credential protection', () => { } }) + it('returns the encoded crop geometry while checking the original element bounds for movement', async () => { + const contents = await openPage() + const measuredClip = { x: 0.1, y: 0.2, width: 1.1, height: 100 } + const capturedClip = { x: 0, y: 0, width: 1.5, height: 100.5 } + respondWith(contents, { + getElementScreenshotRect: { ...measuredClip, element: 'div', refRecovered: false }, + }) + const capture = vi.spyOn(cdp, 'captureScreenshot').mockResolvedValue({ + dataUrl: 'data:image/jpeg;base64,c2lt', + scale: 2, + viewport: { width: 800, height: 600 }, + imageSize: { width: 3, height: 201 }, + clip: capturedClip, + }) + + try { + const result = await driver.executeTool('chat-test', 'browser_screenshot', { elementId: 0 }) + + expect(capture).toHaveBeenCalledWith(contents, measuredClip, expect.any(AbortSignal)) + expect(result).toMatchObject({ + ok: true, + result: { + element: 'div', + clip: capturedClip, + scale: 2, + imageSize: { width: 3, height: 201 }, + }, + }) + expect( + vi + .mocked(contents.executeJavaScript) + .mock.calls.filter(([expression]) => isPageCall(expression, 'getElementScreenshotRect')) + ).toHaveLength(2) + } finally { + capture.mockRestore() + } + }) + it('rejects navigation during an element screenshot measurement', async () => { const contents = await openPage() vi.mocked(contents.executeJavaScript).mockImplementation(async (expression: string) => { @@ -4171,6 +4209,7 @@ describe('credential protection', () => { ok: true, result: { scale: 0.5, + imageSize: { width: 1024, height: 512 }, viewport: { url: 'https://example.com/login', title: 'Example', diff --git a/apps/desktop/src/main/browser-agent/driver.ts b/apps/desktop/src/main/browser-agent/driver.ts index 94b53b9db52..f593a1884ed 100644 --- a/apps/desktop/src/main/browser-agent/driver.ts +++ b/apps/desktop/src/main/browser-agent/driver.ts @@ -1237,7 +1237,7 @@ function unwrapPageResult(result: unknown): unknown { } if (code === 'outside-viewport') { throw new ToolError( - 'That point is outside the visible viewport. Coordinates are CSS pixels within the current viewport — when reading them off a browser_screenshot, divide image pixels by its scale, and scroll the target into view first.' + "That point is outside the visible viewport. Coordinates are CSS pixels within the current viewport — when reading them off a browser_screenshot, follow its caption's X/Y coordinate mapping and crop origin, and scroll the target into view first." ) } if (code === 'ambiguous-editable') { @@ -2714,13 +2714,14 @@ async function executeToolInner( } return { dataUrl: shot.dataUrl, + imageSize: shot.imageSize, viewport, scale, ...(clip ? { element: elementClip?.element, refRecovered: elementClip?.refRecovered === true, - clip, + clip: shot.clip ?? clip, } : {}), } @@ -4218,7 +4219,7 @@ async function executeToolInner( ) if (!isRecordLike(pointTarget) || pointTarget.found !== true) { throw new ToolError( - 'Nothing is rendered at that point. Coordinates are CSS pixels in the current viewport — when reading them off a browser_screenshot, divide image pixels by its scale.' + "Nothing is rendered at that point. Coordinates are CSS pixels in the current viewport — when reading them off a browser_screenshot, follow its caption's X/Y coordinate mapping and crop origin." ) } if (pointTarget.fileInput === true) { @@ -4462,7 +4463,7 @@ async function executeToolInner( ) if (!isRecordLike(probe) || probe.found !== true) { throw new ToolError( - `Nothing is rendered at the ${which} point. Coordinates are CSS pixels in the current viewport — when reading them off a browser_screenshot, divide image pixels by its scale.` + `Nothing is rendered at the ${which} point. Coordinates are CSS pixels in the current viewport — when reading them off a browser_screenshot, follow its caption's X/Y coordinate mapping and crop origin.` ) } return { diff --git a/apps/desktop/src/test/electron-mock.ts b/apps/desktop/src/test/electron-mock.ts index fea9849d085..bd5dbc9918f 100644 --- a/apps/desktop/src/test/electron-mock.ts +++ b/apps/desktop/src/test/electron-mock.ts @@ -173,6 +173,8 @@ function createWebContentsMock() { print: vi.fn(), focus: vi.fn(), invalidate: vi.fn(), + beginFrameSubscription: vi.fn(), + endFrameSubscription: vi.fn(), isFocused: vi.fn(() => false), close: vi.fn(), isDestroyed: vi.fn(() => false), diff --git a/apps/sim/lib/copilot/generated/tool-catalog-v1.ts b/apps/sim/lib/copilot/generated/tool-catalog-v1.ts index d82e0066cb7..360c3710983 100644 --- a/apps/sim/lib/copilot/generated/tool-catalog-v1.ts +++ b/apps/sim/lib/copilot/generated/tool-catalog-v1.ts @@ -500,12 +500,12 @@ export const BrowserClickAt: ToolCatalogEntry = { x: { type: 'number', description: - 'X in CSS pixels within the current viewport. When read off a browser_screenshot, divide the image pixel value by scale and add clip.x when present.', + "X in CSS pixels within the current viewport. When read off a browser_screenshot, follow its caption's X mapping and crop origin.", }, y: { type: 'number', description: - 'Y in CSS pixels within the current viewport, converted from screenshot pixels the same way as x.', + "Y in CSS pixels within the current viewport. When read off a browser_screenshot, follow its caption's Y mapping and crop origin.", }, }, required: ['x', 'y'], @@ -1519,7 +1519,7 @@ export const BrowserScreenshot: ToolCatalogEntry = { elementId: { type: 'number', description: - "Optional element id from the current tab's latest browser_snapshot. When present, capture only the visible portion of that top-page element without scrolling or changing layout. Scroll explicitly first if needed. Framed elements are rejected; use a viewport screenshot for them. Use the returned clip offset when converting image coordinates.", + "Optional element id from the current tab's latest browser_snapshot. When present, capture only the visible portion of that top-page element without scrolling or changing layout. Scroll explicitly first if needed. Framed elements are rejected; use a viewport screenshot for them. Follow the image caption's coordinate mapping, including its crop origin.", }, }, }, diff --git a/apps/sim/lib/copilot/generated/tool-schemas-v1.ts b/apps/sim/lib/copilot/generated/tool-schemas-v1.ts index a1b5c10ecb9..31b27d61213 100644 --- a/apps/sim/lib/copilot/generated/tool-schemas-v1.ts +++ b/apps/sim/lib/copilot/generated/tool-schemas-v1.ts @@ -225,12 +225,12 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { x: { type: 'number', description: - 'X in CSS pixels within the current viewport. When read off a browser_screenshot, divide the image pixel value by scale and add clip.x when present.', + "X in CSS pixels within the current viewport. When read off a browser_screenshot, follow its caption's X mapping and crop origin.", }, y: { type: 'number', description: - 'Y in CSS pixels within the current viewport, converted from screenshot pixels the same way as x.', + "Y in CSS pixels within the current viewport. When read off a browser_screenshot, follow its caption's Y mapping and crop origin.", }, }, required: ['x', 'y'], @@ -1430,7 +1430,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record = { elementId: { type: 'number', description: - "Optional element id from the current tab's latest browser_snapshot. When present, capture only the visible portion of that top-page element without scrolling or changing layout. Scroll explicitly first if needed. Framed elements are rejected; use a viewport screenshot for them. Use the returned clip offset when converting image coordinates.", + "Optional element id from the current tab's latest browser_snapshot. When present, capture only the visible portion of that top-page element without scrolling or changing layout. Scroll explicitly first if needed. Framed elements are rejected; use a viewport screenshot for them. Follow the image caption's coordinate mapping, including its crop origin.", }, }, }, diff --git a/apps/sim/lib/copilot/tools/client/browser-tool-execution.test.ts b/apps/sim/lib/copilot/tools/client/browser-tool-execution.test.ts index 1fdb9165092..a4ee6e8a4f3 100644 --- a/apps/sim/lib/copilot/tools/client/browser-tool-execution.test.ts +++ b/apps/sim/lib/copilot/tools/client/browser-tool-execution.test.ts @@ -1215,6 +1215,8 @@ describe('executeBrowserToolOnClient', () => { it('reshapes a screenshot into an image attachment the model can see', async () => { mockExecuteBrowserTool.mockResolvedValue({ dataUrl: 'data:image/jpeg;base64,/9j/4AAQ', + imageSize: { width: 512, height: 320 }, + scale: 0.5, viewport: { url: 'https://example.com/pricing', title: 'Pricing', @@ -1233,6 +1235,9 @@ describe('executeBrowserToolOnClient', () => { source: { type: 'base64', media_type: 'image/jpeg', data: '/9j/4AAQ' }, }) expect(reported.content).toContain('https://example.com/pricing') + expect(reported.content).toContain('Viewport: 1024 × 640 CSS pixels') + expect(reported.content).toContain('Encoded image: 512 × 320 pixels') + expect(reported.content).toContain('cssX = 0 + imageX / 0.5; cssY = 0 + imageY / 0.5') expect(reported.dataUrl).toBeUndefined() expect(reported.viewport).toMatchObject({ width: 1024, height: 640 }) }) @@ -1253,6 +1258,7 @@ describe('executeBrowserToolOnClient', () => { mockExecuteBrowserTool.mockResolvedValue({ dataUrl: 'data:image/jpeg;base64,/9j/4AAQ', clip: { x: 20, y: 30, width: 200, height: 100 }, + imageSize: { width: 400, height: 200 }, scale: 2, }) executeBrowserToolOnClient(nextToolCallId(), 'browser_screenshot', { elementId: 0 }) @@ -1261,8 +1267,8 @@ describe('executeBrowserToolOnClient', () => { const reported = mockReportCompletion.mock.calls[0][3] expect(reported.clip).toEqual({ x: 20, y: 30, width: 200, height: 100 }) expect(reported.scale).toBe(2) - expect(reported.content).toContain('cssX = clip.x + imageX / scale') - expect(reported.content).toContain('cssY = clip.y + imageY / scale') + expect(reported.content).toContain('cssX = 20 + imageX / 2') + expect(reported.content).toContain('cssY = 30 + imageY / 2') }) it('gives restored-tab switching the renderer navigation budget', async () => { diff --git a/apps/sim/lib/copilot/tools/client/browser-tool-execution.ts b/apps/sim/lib/copilot/tools/client/browser-tool-execution.ts index ced31164d4f..d09705eec7d 100644 --- a/apps/sim/lib/copilot/tools/client/browser-tool-execution.ts +++ b/apps/sim/lib/copilot/tools/client/browser-tool-execution.ts @@ -24,6 +24,7 @@ import { } from '@/lib/copilot/async-runs/lifecycle' import { COPILOT_CONFIRM_API_PATH } from '@/lib/copilot/constants' import { BrowserToolReplayLedger } from '@/lib/copilot/tools/client/browser-tool-replay-ledger' +import { sanitizeBrowserToolResultForModel } from '@/lib/copilot/tools/client/browser-tool-result' import { reportClientToolCompletion, reportClientToolCompletionOnPageExit, @@ -545,59 +546,6 @@ function timeoutForTool(toolName: BrowserToolName, params: Record;base64,` URL into its parts. */ -function parseBase64DataUrl(dataUrl: string): { mediaType: string; data: string } | null { - const match = /^data:([^;,]+);base64,(.+)$/s.exec(dataUrl) - if (!match) return null - return { mediaType: match[1], data: match[2] } -} - -/** - * Reshapes a screenshot into the `attachment` contract the copilot serializes - * into a real image content block, so the model sees the page rather than a - * note about it. The data URL itself never goes inline: `content` is the text - * the model reads beside the image, and the bytes travel under `attachment`. - * - * A malformed data URL degrades to the text note rather than shipping an - * attachment the provider would reject. - */ -function sanitizeResultForModel( - toolName: BrowserToolName, - result: unknown -): Record | undefined { - if (!isRecordLike(result)) { - return result === undefined ? undefined : { value: result } - } - if (toolName === 'browser_screenshot' && typeof result.dataUrl === 'string') { - const { dataUrl, ...rest } = result - const image = parseBase64DataUrl(dataUrl) - if (!image) { - return { - ...rest, - note: 'The screenshot could not be encoded. Use browser_snapshot or browser_read_text instead.', - } - } - const viewport = isRecordLike(rest.viewport) ? rest.viewport : null - const screenshotUrl = - typeof rest.url === 'string' && rest.url - ? rest.url - : viewport && typeof viewport.url === 'string' - ? viewport.url - : '' - const location = screenshotUrl ? ` of ${screenshotUrl}` : '' - const isElementCapture = isRecordLike(rest.clip) - return { - ...rest, - content: `Screenshot${location}. This is the rendered ${isElementCapture ? 'element' : 'viewport'} only — it carries no element ids, so use browser_snapshot before interacting.${isElementCapture ? ' For coordinate actions: cssX = clip.x + imageX / scale; cssY = clip.y + imageY / scale.' : ''}`, - attachment: { - type: 'image', - source: { type: 'base64', media_type: image.mediaType, data: image.data }, - }, - } - } - return result -} - /** * Fire-and-forget entry point invoked by the stream tool-event handler when a * `browser_*` client tool call arrives. @@ -997,7 +945,7 @@ async function doExecuteBrowserTool( : effectUnconfirmed ? 'Browser input completed; its effect is unconfirmed. Inspect the current state before retrying.' : 'Browser action completed', - data: sanitizeResultForModel(toolName, result), + data: sanitizeBrowserToolResultForModel(toolName, result), }, 'Failed to report successful browser tool completion' ) diff --git a/apps/sim/lib/copilot/tools/client/browser-tool-result.test.ts b/apps/sim/lib/copilot/tools/client/browser-tool-result.test.ts new file mode 100644 index 00000000000..fee5723b4e0 --- /dev/null +++ b/apps/sim/lib/copilot/tools/client/browser-tool-result.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, it } from 'vitest' +import { sanitizeBrowserToolResultForModel } from '@/lib/copilot/tools/client/browser-tool-result' + +describe('browser screenshot model projection', () => { + it('keeps an image usable when an older desktop omits coordinate metadata', () => { + const result = sanitizeBrowserToolResultForModel('browser_screenshot', { + dataUrl: 'data:image/jpeg;base64,/9j/4AAQ', + }) + expect(result?.attachment).toBeDefined() + expect(result?.content).toContain('coordinate mapping is unavailable') + expect(result?.content).not.toContain('cssX =') + }) + + it.each([0, -1, Number.NaN, Number.POSITIVE_INFINITY, '0.5'])( + 'does not publish an invalid scale %s', + (scale) => { + const result = sanitizeBrowserToolResultForModel('browser_screenshot', { + dataUrl: 'data:image/jpeg;base64,/9j/4AAQ', + scale, + viewport: { width: 1600, height: 900 }, + }) + expect(result?.content).toContain('Viewport: 1600 × 900 CSS pixels') + expect(result?.content).toContain('coordinate mapping is unavailable') + expect(result?.content).not.toContain('cssX =') + } + ) + + it('does not invent a crop origin or encode malformed dimensions into the caption', () => { + const result = sanitizeBrowserToolResultForModel('browser_screenshot', { + dataUrl: 'data:image/jpeg;base64,/9j/4AAQ', + scale: 2, + clip: { x: '10', y: 20 }, + viewport: { width: 0, height: 900 }, + imageSize: { width: Number.POSITIVE_INFINITY, height: 640 }, + }) + expect(result?.attachment).toBeDefined() + expect(result?.content).toContain('coordinate mapping is unavailable') + expect(result?.content).not.toMatch(/Viewport:|Encoded image:|Crop origin:|cssX =/) + }) + + it('maps each crop axis independently when pixel rounding changes its aspect ratio', () => { + const result = sanitizeBrowserToolResultForModel('browser_screenshot', { + dataUrl: 'data:image/jpeg;base64,/9j/4AAQ', + scale: 2.5, + clip: { x: 0, y: 20, width: 1.5, height: 100 }, + imageSize: { width: 3, height: 200 }, + }) + expect(result?.content).toContain('Crop origin: (0, 20) in viewport CSS pixels') + expect(result?.content).toContain('cssX = 0 + imageX / 2; cssY = 20 + imageY / 2') + }) + + it('keeps a legacy crop image without publishing its unverified scalar mapping', () => { + const result = sanitizeBrowserToolResultForModel('browser_screenshot', { + dataUrl: 'data:image/jpeg;base64,/9j/4AAQ', + scale: 3 / 1.1, + clip: { x: 0.1, y: 20, width: 1.1, height: 100 }, + }) + expect(result?.attachment).toBeDefined() + expect(result?.content).toContain('coordinate mapping is unavailable') + expect(result?.content).not.toContain('cssX =') + }) + + it('uses both encoded dimensions for a resized viewport', () => { + const result = sanitizeBrowserToolResultForModel('browser_screenshot', { + dataUrl: 'data:image/jpeg;base64,/9j/4AAQ', + scale: 0.5, + viewport: { width: 1600, height: 901 }, + imageSize: { width: 800, height: 451 }, + }) + expect(result?.content).toContain(`cssX = 0 + imageX / 0.5; cssY = 0 + imageY / ${451 / 901}`) + }) + + it('withholds a legacy viewport scalar when encoded dimensions are unavailable', () => { + const result = sanitizeBrowserToolResultForModel('browser_screenshot', { + dataUrl: 'data:image/jpeg;base64,/9j/4AAQ', + scale: 0.5, + viewport: { width: 2048, height: 1025 }, + }) + expect(result?.attachment).toBeDefined() + expect(result?.scale).toBe(0.5) + expect(result?.content).toContain('Viewport: 2048 × 1025 CSS pixels') + expect(result?.content).toContain('coordinate mapping is unavailable') + expect(result?.content).not.toContain('cssY =') + }) + + it('leaves non-image tool results unchanged', () => { + const result = { outline: 'button "Continue" [ref=3]' } + expect(sanitizeBrowserToolResultForModel('browser_snapshot', result)).toBe(result) + expect(sanitizeBrowserToolResultForModel('browser_snapshot', undefined)).toBeUndefined() + }) +}) diff --git a/apps/sim/lib/copilot/tools/client/browser-tool-result.ts b/apps/sim/lib/copilot/tools/client/browser-tool-result.ts new file mode 100644 index 00000000000..7ffafbcd616 --- /dev/null +++ b/apps/sim/lib/copilot/tools/client/browser-tool-result.ts @@ -0,0 +1,81 @@ +import type { BrowserToolName } from '@sim/browser-protocol' +import { isRecordLike } from '@sim/utils/object' + +function finiteNumber(value: unknown): value is number { + return typeof value === 'number' && Number.isFinite(value) +} + +function imageDimensions(value: unknown): { width: number; height: number } | null { + if ( + !isRecordLike(value) || + !finiteNumber(value.width) || + !finiteNumber(value.height) || + value.width <= 0 || + value.height <= 0 + ) { + return null + } + return { width: value.width, height: value.height } +} + +/** Projects image bytes and coordinate metadata into the model's image-content contract. */ +export function sanitizeBrowserToolResultForModel( + toolName: BrowserToolName, + result: unknown +): Record | undefined { + if (!isRecordLike(result)) { + return result === undefined ? undefined : { value: result } + } + if (toolName !== 'browser_screenshot' || typeof result.dataUrl !== 'string') return result + + const { dataUrl, ...rest } = result + const image = /^data:([^;,]+);base64,(.+)$/s.exec(dataUrl) + if (!image) { + return { + ...rest, + note: 'The screenshot could not be encoded. Use browser_snapshot or browser_read_text instead.', + } + } + const viewport = isRecordLike(rest.viewport) ? rest.viewport : null + const screenshotUrl = + typeof rest.url === 'string' && rest.url + ? rest.url + : viewport && typeof viewport.url === 'string' + ? viewport.url + : '' + const location = screenshotUrl ? ` of ${screenshotUrl}` : '' + const clip = isRecordLike(rest.clip) ? rest.clip : null + const cropSize = imageDimensions(clip) + const viewportSize = imageDimensions(viewport) + const imageSize = imageDimensions(rest.imageSize) + const capturedSize = clip ? cropSize : viewportSize + const scaleX = imageSize && capturedSize ? imageSize.width / capturedSize.width : null + const scaleY = imageSize && capturedSize ? imageSize.height / capturedSize.height : null + const hasScale = finiteNumber(scaleX) && scaleX > 0 && finiteNumber(scaleY) && scaleY > 0 + const origin = clip + ? finiteNumber(clip.x) && finiteNumber(clip.y) + ? { x: clip.x, y: clip.y } + : null + : { x: 0, y: 0 } + const content = [ + `Screenshot${location}. This is the rendered ${clip ? 'element' : 'viewport'} only — it carries no element ids. Use browser_snapshot for element-ref actions and the mapping below for coordinate actions.`, + viewportSize && `Viewport: ${viewportSize.width} × ${viewportSize.height} CSS pixels.`, + imageSize && `Encoded image: ${imageSize.width} × ${imageSize.height} pixels.`, + hasScale && `Image scale: X=${scaleX}, Y=${scaleY} encoded image pixels per CSS pixel.`, + cropSize && `Crop size: ${cropSize.width} × ${cropSize.height} CSS pixels.`, + clip && origin && `Crop origin: (${origin.x}, ${origin.y}) in viewport CSS pixels.`, + hasScale && origin + ? `Coordinate actions use viewport CSS pixels: cssX = ${origin.x} + imageX / ${scaleX}; cssY = ${origin.y} + imageY / ${scaleY}. imageX/imageY refer to the encoded image before any display resizing.` + : 'Screenshot coordinate mapping is unavailable; use browser_snapshot element references or take a new viewport screenshot before coordinate actions.', + ] + .filter(Boolean) + .join(' ') + return { + ...rest, + content, + attachment: { + type: 'image', + source: { type: 'base64', media_type: image[1], data: image[2] }, + }, + } +} diff --git a/scripts/check-tool-registry-boundary.baseline.json b/scripts/check-tool-registry-boundary.baseline.json index e2ca0a52ad2..9be3e4c4193 100644 --- a/scripts/check-tool-registry-boundary.baseline.json +++ b/scripts/check-tool-registry-boundary.baseline.json @@ -93,9 +93,9 @@ "gateways": {} }, "app/workspace/[workspaceId]/chat/[chatId]/page.tsx": { - "modules": 3125, + "modules": 3126, "gateways": { - "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1521, + "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1522, "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx": 961, "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/index.ts": 795, "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/index.ts": 790, @@ -183,9 +183,9 @@ "gateways": {} }, "app/workspace/[workspaceId]/home/page.tsx": { - "modules": 3125, + "modules": 3126, "gateways": { - "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1521, + "apps/sim/app/workspace/[workspaceId]/home/home.tsx": 1522, "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx": 961, "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/index.ts": 795, "apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/index.ts": 790, @@ -613,23 +613,23 @@ } }, "app/workspace/[workspaceId]/w/[workflowId]/page.tsx": { - "modules": 2168, + "modules": 2169, "gateways": { - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 2167, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx": 2168, "apps/sim/triggers/registry.ts": 522, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 379, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 380, "apps/sim/blocks/registry.ts": 350, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 342, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 343, "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 275, "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/index.ts": 168, "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx": 149 } }, "app/workspace/[workspaceId]/w/page.tsx": { - "modules": 2149, + "modules": 2150, "gateways": { - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 963, - "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 633, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/index.ts": 964, + "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/index.ts": 634, "apps/sim/triggers/registry.ts": 522, "apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/index.ts": 355, "apps/sim/blocks/registry.ts": 350, From cb671455dd97956478464d2fb060ab5f844606bd Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 16 Sep 2026 14:49:23 -0700 Subject: [PATCH 23/43] fix(search): surface Google service account authorization errors (#7893) * fix(search): surface Google service account authorization errors * fix(search): preserve unrecognized Google token failures * fix(search): handle delegated token errors during setup * fix(search): clarify service account delegation guidance --- apps/docs/content/docs/search/gmail.mdx | 2 + .../content/docs/search/google-calendar.mdx | 2 + .../docs/content/docs/search/google-drive.mdx | 11 + .../knowledge/application/connectors.test.ts | 253 ++++++++++++++++++ .../lib/knowledge/application/connectors.ts | 53 +++- apps/sim/lib/oauth/credential-service.test.ts | 72 +++++ apps/sim/lib/oauth/credential-service.ts | 31 ++- 7 files changed, 409 insertions(+), 15 deletions(-) diff --git a/apps/docs/content/docs/search/gmail.mdx b/apps/docs/content/docs/search/gmail.mdx index c030c526062..e5c3f63adbe 100644 --- a/apps/docs/content/docs/search/gmail.mdx +++ b/apps/docs/content/docs/search/gmail.mdx @@ -84,6 +84,8 @@ Enter the Client ID and these exact scopes, separated by a comma: https://www.googleapis.com/auth/gmail.readonly,https://www.googleapis.com/auth/admin.directory.user.readonly ``` +For a key shared with Drive and Calendar, use the [combined six-scope list](/search/google-drive#one-service-account-for-drive-gmail-and-calendar). + Select **Authorize**, then **View details** to confirm both scopes were saved. If the same client also indexes Drive or Calendar, retain those services' required scopes. These Gmail crawl scopes do not allow sending or modifying mail. If your organization requires multi-party approval, another super administrator must approve the request. Delegation can take up to 24 hours to propagate. See Google's [delegation guide](https://knowledge.workspace.google.com/admin/apps/control-api-access-with-domain-wide-delegation). diff --git a/apps/docs/content/docs/search/google-calendar.mdx b/apps/docs/content/docs/search/google-calendar.mdx index 2f302719ebc..5ce4684134c 100644 --- a/apps/docs/content/docs/search/google-calendar.mdx +++ b/apps/docs/content/docs/search/google-calendar.mdx @@ -86,6 +86,8 @@ Enter the Client ID and these exact comma-separated **OAuth scopes**: https://www.googleapis.com/auth/calendar.events.readonly,https://www.googleapis.com/auth/admin.directory.user.readonly ``` +For a key shared with Drive and Gmail, use the [combined six-scope list](/search/google-drive#one-service-account-for-drive-gmail-and-calendar). + Select **Authorize** and verify both scopes under **View details**. If you reuse a Drive or Gmail service account, retain its existing delegated scopes and add any missing Calendar scopes. An existing Drive authorization alone does not grant Calendar access. Delegation can take up to 24 hours to propagate; organizations requiring multi-party approval need another super administrator to approve the change. See [Google's delegation guide](https://knowledge.workspace.google.com/admin/apps/control-api-access-with-domain-wide-delegation). The **Directory administrator email** must be an active Workspace administrator with permission to read users. A super administrator has this permission; a custom administrator role can supply it. This identity lists the directory. Sim obtains a separate read-only Calendar token for each selected user; it does not read everyone's events as the administrator. diff --git a/apps/docs/content/docs/search/google-drive.mdx b/apps/docs/content/docs/search/google-drive.mdx index 0565a5289f7..e647b5544c8 100644 --- a/apps/docs/content/docs/search/google-drive.mdx +++ b/apps/docs/content/docs/search/google-drive.mdx @@ -121,6 +121,16 @@ Invite teammates through **Settings → Members → Invite**, using their Google +## One service account for Drive, Gmail, and Calendar + +You can reuse one JSON key for all three central connectors. Authorize this combined list on the same numeric **Client ID**, and enable **Google Drive API**, **Gmail API**, **Google Calendar API**, and **Admin SDK API** in its Cloud project: + +```text +https://www.googleapis.com/auth/drive.readonly,https://www.googleapis.com/auth/gmail.readonly,https://www.googleapis.com/auth/calendar.events.readonly,https://www.googleapis.com/auth/admin.directory.user.readonly,https://www.googleapis.com/auth/admin.directory.group.readonly,https://www.googleapis.com/auth/admin.directory.domain.readonly +``` + +Under **View details**, verify all six scopes are saved. Gmail and Calendar succeeding does not verify Drive's group and domain permissions. Each connector still needs its own source configuration in Sim. + ## Source options An admin opens **Settings → Sources → Google Drive** to open its configuration list. Each row shows **Member accounts** or **Service account** beside its sync status. Open a connection's **Settings** tab to edit its filters, then select **Save**. **Documents** shows indexed files and **Sync history** shows recent runs. @@ -147,6 +157,7 @@ Search schedules syncs hourly. Central crawls revisit the selected users' files | Problem | Next step | | --- | --- | +| Google rejects authorization (`unauthorized_client`) | In **Manage Domain Wide Delegation**, verify the numeric **Client ID** matches `client_id` in the JSON key uploaded to Sim and all required scopes appear under **View details**. Check pending approval and allow time for recent changes to propagate. Changing the OAuth consent screen alone does not authorize delegation. | | Directory access failed | Check all four delegated scopes and the **Directory administrator email** user's administrator privileges. A normal Google OAuth credential cannot supply this central Search path. | | Missing files in a central crawl | Check **Users**, folder and file-type filters, and whether selected active Workspace users can download the file and read its permissions. Opening a file alone does not prove either. Check Sync history for errors. Files reachable only by excluded or inactive accounts are not crawled; files with unverified permissions stay hidden. | | User not found or inactive | Use a primary email in the same Google Workspace customer. Aliases, external or guest accounts, suspended users, and archived users cannot be selected for crawling. | diff --git a/apps/sim/lib/knowledge/application/connectors.test.ts b/apps/sim/lib/knowledge/application/connectors.test.ts index b57dbaa3c20..918a76321fe 100644 --- a/apps/sim/lib/knowledge/application/connectors.test.ts +++ b/apps/sim/lib/knowledge/application/connectors.test.ts @@ -107,6 +107,15 @@ vi.mock('@/lib/credentials/application/organization-credentials', () => ({ })) vi.mock('@/lib/oauth/credential-service', () => ({ + ServiceAccountTokenError: class extends Error { + constructor( + readonly statusCode: number, + readonly errorDescription: string, + readonly errorCode?: string + ) { + super(errorDescription) + } + }, resolveCredentialTokenBundle: mocks.resolveTokenBundle, resolveOAuthAccountId: vi.fn(async () => null), getServiceAccountToken: vi.fn(), @@ -145,6 +154,7 @@ vi.mock('@/connectors/registry.server', () => ({ 'https://www.googleapis.com/auth/admin.directory.group.readonly', 'https://www.googleapis.com/auth/admin.directory.domain.readonly', ], + serviceAccountDelegationScopes: ['https://www.googleapis.com/auth/drive.readonly'], serviceAccountSubjectFieldId: 'adminEmail', }, validateConfig: mocks.validateConnectorConfig, @@ -167,11 +177,20 @@ import { updateKnowledgeConnectorDocuments, validateConnectorSourceConfig, } from '@/lib/knowledge/application/connectors' +import type { ConnectorAccessToken } from '@/lib/knowledge/connectors/access-token' import { MAX_KNOWLEDGE_CONNECTOR_DOCUMENT_SEARCH_LENGTH } from '@/lib/knowledge/constants' +import { classifyKnowledgeFailure } from '@/lib/knowledge/orchestration/shared' +import { + getServiceAccountToken, + resolveOAuthAccountId, + ServiceAccountTokenError, +} from '@/lib/oauth/credential-service' import * as githubInstallation from '@/lib/oauth/github-installation' import { capabilityRefusal } from '@/lib/permission-groups/capability-assertions' import { DEFAULT_PERMISSION_GROUP_CONFIG } from '@/lib/permission-groups/fields' import { confluenceConnectorMeta } from '@/connectors/confluence/meta' +import { gmailConnectorMeta } from '@/connectors/gmail/meta' +import { googleCalendarConnectorMeta } from '@/connectors/google-calendar/meta' import { googleDriveConnectorMeta } from '@/connectors/google-drive/meta' const crossWorkspaceContext = { @@ -1624,6 +1643,240 @@ describe('organization connector credential authorization', () => { expect(mocks.resolveTokenBundle).not.toHaveBeenCalled() }) + it.each([googleDriveConnectorMeta, gmailConnectorMeta, googleCalendarConnectorMeta])( + 'projects $name token rejections as safe setup errors', + async ({ auth }) => { + mocks.resolveTokenBundle.mockRejectedValueOnce( + new ServiceAccountTokenError(401, 'private provider payload', 'unauthorized_client') + ) + const error = await resolveConnectorCredentialAccessToken({ ...input, auth }).catch( + (error: unknown) => error + ) + expect(error).toBeInstanceOf(OrchestrationError) + expect(internalOrchestrationErrorPolicy.project(error)).toMatchObject({ + status: 400, + body: { error: expect.stringContaining('(unauthorized_client)') }, + }) + expect((error as Error).message).toContain('numeric client ID') + expect((error as Error).message).toContain( + "exact domain-wide delegation scopes in this connector's service-account setup section" + ) + expect((error as Error).message).not.toContain('private provider payload') + expect(mocks.authorizeOrganizationCredentialUse).toHaveBeenCalledOnce() + } + ) + + it.each([ + [400, 'invalid_grant', 'JSON key'], + [ + 400, + 'invalid_scope', + "exact domain-wide delegation scopes in this connector's service-account setup section", + ], + [403, 'access_denied', 'API access policies'], + ])('classifies Google %s %s without exposing provider text', async (status, code, guidance) => { + mocks.resolveTokenBundle.mockRejectedValueOnce( + new ServiceAccountTokenError(status, 'private provider payload', code) + ) + const error = await resolveConnectorCredentialAccessToken(input).catch( + (error: unknown) => error + ) + expect(error).toMatchObject({ code: 'validation', message: expect.stringContaining(guidance) }) + expect((error as Error).message).not.toContain('private provider payload') + }) + + it.each([googleDriveConnectorMeta, gmailConnectorMeta, googleCalendarConnectorMeta])( + 'maps $name delegated token failures after directory authorization succeeds', + async ({ auth }) => { + vi.mocked(resolveOAuthAccountId).mockResolvedValueOnce({ + accountId: '', + usedCredentialTable: true, + credentialId: credential.id, + credentialType: 'service_account', + providerId: credential.providerId, + }) + vi.mocked(getServiceAccountToken).mockRejectedValueOnce( + new ServiceAccountTokenError(401, 'private delegated response', 'unauthorized_client') + ) + const resolved = await resolveConnectorCredentialAccessToken({ ...input, auth }) + expect(resolved?.accessToken).toBe('organization-token') + expect(getServiceAccountToken).not.toHaveBeenCalled() + if (!resolved?.getDelegatedAccessToken) throw new Error('Expected delegated token resolver') + await expect(resolved.getDelegatedAccessToken('member@example.com')).rejects.toMatchObject({ + code: 'validation', + message: expect.stringContaining('(unauthorized_client)'), + }) + expect(getServiceAccountToken).toHaveBeenCalledWith( + credential.id, + auth.mode === 'oauth' ? auth.serviceAccountDelegationScopes : undefined, + 'member@example.com' + ) + } + ) + + it('preserves successful delegated token reads and unexpected failures', async () => { + vi.mocked(resolveOAuthAccountId).mockResolvedValueOnce({ + accountId: '', + usedCredentialTable: true, + credentialId: credential.id, + credentialType: 'service_account', + providerId: credential.providerId, + }) + const resolved = await resolveConnectorCredentialAccessToken(input) + if (!resolved?.getDelegatedAccessToken) throw new Error('Expected delegated token resolver') + vi.mocked(getServiceAccountToken).mockResolvedValueOnce('delegated-token') + await expect(resolved.getDelegatedAccessToken('member@example.com')).resolves.toBe( + 'delegated-token' + ) + for (const error of [ + new ServiceAccountTokenError(401, 'private delegated response', 'unknown-code'), + new ServiceAccountTokenError(503, 'private delegated response', 'unauthorized_client'), + new TypeError('Network request failed'), + ]) { + vi.mocked(getServiceAccountToken).mockRejectedValueOnce(error) + await expect(resolved.getDelegatedAccessToken('member@example.com')).rejects.toBe(error) + } + }) + + it('passes safe delegated errors to configuration validation', async () => { + vi.mocked(resolveOAuthAccountId).mockResolvedValueOnce({ + accountId: '', + usedCredentialTable: true, + credentialId: credential.id, + credentialType: 'service_account', + providerId: credential.providerId, + }) + vi.mocked(getServiceAccountToken).mockRejectedValueOnce( + new ServiceAccountTokenError(401, 'private delegated response', 'unauthorized_client') + ) + mocks.validateConnectorConfig.mockImplementationOnce( + async (_token: string, _config: unknown, context: ConnectorAccessToken) => { + if (!context.getDelegatedAccessToken) throw new Error('Expected delegated token resolver') + try { + await context.getDelegatedAccessToken('member@example.com') + return { valid: true } + } catch (error) { + if (!(error instanceof Error)) throw error + return { valid: false, error: error.message } + } + } + ) + const rejection = await validateConnectorSourceConfig({ + principal, + organizationId: 'org', + actingUserId: principal.userId, + requestId: 'request', + sourceConfig: input.sourceConfig, + connector: { + connectorType: 'google_drive', + credentialId: credential.id, + encryptedApiKey: null, + accessMode: 'admin', + } as Parameters[0]['connector'], + }) + expect(rejection).toMatchObject({ + errorCode: 'validation', + message: expect.stringContaining('(unauthorized_client)'), + }) + expect(rejection?.message).not.toContain('private delegated response') + }) + + it.each([400, 401, 403])( + 'preserves unrecognized Google %s responses instead of assuming a configuration error', + async (status) => { + for (const code of [undefined, 'unknown-private-code', 'server_error']) { + const error = new ServiceAccountTokenError(status, 'private provider payload', code) + mocks.resolveTokenBundle.mockRejectedValueOnce(error) + await expect(resolveConnectorCredentialAccessToken(input)).rejects.toBe(error) + expect(internalOrchestrationErrorPolicy.project(error)).toBeNull() + } + } + ) + + it.each([429, 500, 503])( + 'preserves Google %s failures instead of blaming configuration', + async (status) => { + const error = new ServiceAccountTokenError(status, 'private provider payload', 'server_error') + mocks.resolveTokenBundle.mockRejectedValueOnce(error) + await expect(resolveConnectorCredentialAccessToken(input)).rejects.toBe(error) + expect(internalOrchestrationErrorPolicy.project(error)).toBeNull() + } + ) + + it('preserves unexpected token failures as internal errors', async () => { + const error = new TypeError('private network failure') + mocks.resolveTokenBundle.mockRejectedValueOnce(error) + await expect(resolveConnectorCredentialAccessToken(input)).rejects.toBe(error) + expect(internalOrchestrationErrorPolicy.project(error)).toBeNull() + }) + + it('keeps authorization errors actionable through connector creation orchestration', async () => { + queueTableRows(member, [{ role: 'admin' }]) + queueTableRows(member, [{ role: 'admin' }]) + mocks.resolveKnowledgeBase.mockResolvedValue({ + organizationId: 'org', + knowledgeBaseId: 'org-index', + knowledgeBase: { id: 'org-index', name: 'Search', isSearchIndex: true }, + }) + mocks.resolveTokenBundle.mockRejectedValueOnce( + new ServiceAccountTokenError(401, 'private provider payload', 'unauthorized_client') + ) + mocks.createConnector.mockImplementationOnce( + async (createInput: { resolveAccessToken(id: string): Promise }) => { + try { + await createInput.resolveAccessToken(credential.id) + throw new Error('Unexpected successful token exchange') + } catch (error) { + return classifyKnowledgeFailure(error, 'request', 'Create connector') + } + } + ) + const error = await createKnowledgeConnector + .execute({ + principal, + input: { + knowledgeBaseId: 'org-index', + assertedOrganizationId: 'org', + connectorType: 'google_drive', + credentialId: credential.id, + accessMode: 'admin', + sourceConfig: input.sourceConfig, + syncIntervalMinutes: 60, + }, + }) + .catch((error: unknown) => error) + expect(internalOrchestrationErrorPolicy.project(error)).toMatchObject({ + status: 400, + body: { error: expect.stringContaining('(unauthorized_client)') }, + }) + expect(mocks.recordAudit).not.toHaveBeenCalled() + }) + + it('returns an actionable error before saving a configuration edit', async () => { + mocks.resolveTokenBundle.mockRejectedValueOnce( + new ServiceAccountTokenError(401, 'private provider payload', 'unauthorized_client') + ) + await expect( + validateConnectorSourceConfig({ + principal, + organizationId: 'org', + actingUserId: principal.userId, + requestId: 'request', + sourceConfig: input.sourceConfig, + connector: { + connectorType: 'google_drive', + credentialId: credential.id, + encryptedApiKey: null, + accessMode: 'admin', + } as Parameters[0]['connector'], + }) + ).rejects.toMatchObject({ + code: 'validation', + message: expect.stringContaining('(unauthorized_client)'), + }) + expect(mocks.validateConnectorConfig).not.toHaveBeenCalled() + }) + it('does not mint a token after the credential creator leaves the organization', async () => { mocks.resolveTokenIdentity.mockResolvedValueOnce(null) await expect(resolveConnectorCredentialAccessToken(input)).resolves.toBeNull() diff --git a/apps/sim/lib/knowledge/application/connectors.ts b/apps/sim/lib/knowledge/application/connectors.ts index 990f91a282d..dee4bb40742 100644 --- a/apps/sim/lib/knowledge/application/connectors.ts +++ b/apps/sim/lib/knowledge/application/connectors.ts @@ -107,6 +107,7 @@ import { requireOrganizationSearchApproval } from '@/lib/knowledge/search/integr import { escapeLikePattern } from '@/lib/knowledge/tags/utils' import { isMemberSyncStatus } from '@/lib/knowledge/types' import { credentialProviderMatchesService, type ServiceProviderIdentity } from '@/lib/oauth' +import { ServiceAccountTokenError } from '@/lib/oauth/credential-service' import { CAPABILITY_RULES, refuseCapability } from '@/lib/permission-groups/capabilities' import { resolvePermissionGroupConfig } from '@/lib/permission-groups/config-scope.server' import { getUserPermissionConfigForOrganization } from '@/lib/permission-groups/resolve.server' @@ -321,15 +322,57 @@ export async function resolveConnectorCredentialAccessToken(input: { }): Promise { const identity = await resolveAuthorizedConnectorCredentialIdentity(input) if (!identity) return null - const resolved = await resolveConnectorAccessToken({ + return resolveConnectorValidationAccessToken({ auth: input.auth, accessMode: input.accessMode, connector: { credentialId: input.credentialId, encryptedApiKey: null }, userId: identity.kind === 'oauth' ? identity.userId : input.actingUserId, requestId: input.requestId, sourceConfig: input.sourceConfig, - }).catch(rethrowGitHubInstallationSourceError) - return resolved + }) +} + +/** Exposes actionable credential refusals without returning raw provider payloads. */ +function rethrowConnectorCredentialError(error: unknown): never { + if (error instanceof ServiceAccountTokenError && [400, 401, 403].includes(error.statusCode)) { + let message: string + switch (error.errorCode) { + case 'unauthorized_client': + message = + "Google rejected service-account authorization (unauthorized_client). In Google Admin, authorize the JSON key's numeric client ID with the exact domain-wide delegation scopes in this connector's service-account setup section. Verify the delegated user's Workspace email and allow time for recent delegation changes to propagate." + break + case 'invalid_grant': + message = + "Google rejected the service-account grant (invalid_grant). Check that the JSON key is valid and the delegated user's primary Workspace email is correct." + break + case 'invalid_scope': + message = + "Google rejected the service-account scopes (invalid_scope). In Google Admin, authorize the exact domain-wide delegation scopes in this connector's service-account setup section." + break + case 'access_denied': + message = + 'Google denied service-account access (access_denied). Ask your Workspace administrator to check API access policies and domain-wide delegation.' + break + default: + throw error + } + throw new OrchestrationError('validation', message) + } + rethrowGitHubInstallationSourceError(error) +} + +/** Applies setup error handling to initial tokens and later delegated user probes. */ +async function resolveConnectorValidationAccessToken( + params: Parameters[0] +): Promise { + const resolved = await resolveConnectorAccessToken(params).catch(rethrowConnectorCredentialError) + const getDelegatedAccessToken = resolved?.getDelegatedAccessToken + if (!resolved || !getDelegatedAccessToken) return resolved + return { + ...resolved, + getDelegatedAccessToken: (subject) => + getDelegatedAccessToken(subject).catch(rethrowConnectorCredentialError), + } } export async function validateConnectorSourceConfig(input: { @@ -419,14 +462,14 @@ export async function validateConnectorSourceConfig(input: { if (identity.kind === 'oauth') tokenUserId = identity.userId } - const resolved = await resolveConnectorAccessToken({ + const resolved = await resolveConnectorValidationAccessToken({ auth: connectorConfig.auth, accessMode, connector: input.connector, userId: tokenUserId, requestId: input.requestId, sourceConfig: input.sourceConfig, - }).catch(rethrowGitHubInstallationSourceError) + }) if (!resolved) { return { message: 'Failed to refresh access token. Please reconnect your account.', diff --git a/apps/sim/lib/oauth/credential-service.test.ts b/apps/sim/lib/oauth/credential-service.test.ts index d28450cba11..a9f10ec759f 100644 --- a/apps/sim/lib/oauth/credential-service.test.ts +++ b/apps/sim/lib/oauth/credential-service.test.ts @@ -80,6 +80,7 @@ import { getServiceAccountToken, refreshTokenIfNeeded, resolveCredentialTokenBundle, + ServiceAccountTokenError, } from '@/lib/oauth/credential-service' import { GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID } from '@/lib/oauth/types' @@ -453,4 +454,75 @@ describe('Google service-account token minting', () => { expect(mocks.decryptSecret).toHaveBeenCalledTimes(1) expect(fetchMock).toHaveBeenCalledTimes(1) }) + + it('retains the Google error code for actionable setup failures', async () => { + queueTableRows(credential, [row]) + fetchMock.mockResolvedValueOnce( + Response.json( + { error: 'unauthorized_client', error_description: RAW_PROVIDER_ERROR }, + { status: 401 } + ) + ) + const error = await getServiceAccountToken( + 'credential-1', + [driveScope], + 'admin@example.com' + ).catch((error: unknown) => error) + expect(error).toBeInstanceOf(ServiceAccountTokenError) + expect(error).toMatchObject({ + statusCode: 401, + errorCode: 'unauthorized_client', + errorDescription: RAW_PROVIDER_ERROR, + }) + }) + + it('keeps token errors private for selectors', async () => { + queueTableRows(credential, [row]) + fetchMock.mockResolvedValueOnce( + Response.json( + { error: 'unauthorized_client', error_description: RAW_PROVIDER_ERROR }, + { status: 401 } + ) + ) + await expect( + getServiceAccountToken('credential-1', [driveScope], 'admin@example.com', { + privacyMode: 'selector', + }) + ).rejects.toMatchObject({ + statusCode: 401, + errorCode: undefined, + errorDescription: 'Token exchange failed: 401', + }) + expect(JSON.stringify(mocks.logger.error.mock.calls)).not.toContain(RAW_PROVIDER_ERROR) + }) + + it.each([ + 'Unavailable', + 'null', + '{"error":42,"error_description":{}}', + '{"error_description":""}', + ])('handles malformed provider errors without losing the HTTP status: %s', async (body) => { + queueTableRows(credential, [row]) + fetchMock.mockResolvedValueOnce(new Response(body, { status: 503 })) + await expect(getServiceAccountToken('credential-1', [driveScope])).rejects.toMatchObject({ + statusCode: 503, + errorCode: undefined, + errorDescription: 'Token exchange failed: 503', + }) + }) + + it('continues to hide invalid-signature details', async () => { + queueTableRows(credential, [row]) + fetchMock.mockResolvedValueOnce( + Response.json( + { error: 'invalid_grant', error_description: 'Invalid signature: private key details' }, + { status: 400 } + ) + ) + await expect(getServiceAccountToken('credential-1', [driveScope])).rejects.toMatchObject({ + statusCode: 400, + errorCode: 'invalid_grant', + errorDescription: 'Invalid account credentials.', + }) + }) }) diff --git a/apps/sim/lib/oauth/credential-service.ts b/apps/sim/lib/oauth/credential-service.ts index 33181593f2d..7cfee32eaea 100644 --- a/apps/sim/lib/oauth/credential-service.ts +++ b/apps/sim/lib/oauth/credential-service.ts @@ -86,7 +86,8 @@ function privateCredentialIdentity(namespace: string, value: string): string { export class ServiceAccountTokenError extends Error { constructor( public readonly statusCode: number, - public readonly errorDescription: string + public readonly errorDescription: string, + public readonly errorCode?: string ) { super(errorDescription) this.name = 'ServiceAccountTokenError' @@ -296,22 +297,32 @@ export async function getServiceAccountToken( ...(options?.privacyMode === 'selector' ? {} : { body: errorBody }), }) let description = `Token exchange failed: ${response.status}` + let errorCode: string | undefined if (options?.privacyMode !== 'selector') { try { - const parsed = JSON.parse(errorBody) as { error_description?: string } - if (parsed.error_description) { - const raw = parsed.error_description - if (raw.includes('SignatureException') || raw.includes('Invalid signature')) { - description = 'Invalid account credentials.' - } else { - description = raw + const parsed: unknown = JSON.parse(errorBody) + if (typeof parsed === 'object' && parsed !== null) { + if ('error' in parsed && typeof parsed.error === 'string') { + errorCode = parsed.error + } + if ( + 'error_description' in parsed && + typeof parsed.error_description === 'string' && + parsed.error_description.length > 0 + ) { + const raw = parsed.error_description + if (raw.includes('SignatureException') || raw.includes('Invalid signature')) { + description = 'Invalid account credentials.' + } else { + description = raw + } } } } catch { - // use default description + /** Retain the status-based description when Google returns a non-JSON error. */ } } - throw new ServiceAccountTokenError(response.status, description) + throw new ServiceAccountTokenError(response.status, description, errorCode) } const tokenData = (await response.json()) as { access_token: string } From 224389020517bffb47271696bb641e1a6298c290 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 16 Sep 2026 15:35:14 -0700 Subject: [PATCH 24/43] fix(workflows): resolve finished resume runs on the run status endpoint (#7894) --- .../executor/execution-status.test.ts | 180 ++++++++++++++++++ .../workflows/executor/execution-status.ts | 97 +++++++++- 2 files changed, 268 insertions(+), 9 deletions(-) diff --git a/apps/sim/lib/workflows/executor/execution-status.test.ts b/apps/sim/lib/workflows/executor/execution-status.test.ts index e593b9614fc..1a086357676 100644 --- a/apps/sim/lib/workflows/executor/execution-status.test.ts +++ b/apps/sim/lib/workflows/executor/execution-status.test.ts @@ -430,3 +430,183 @@ describe('getWorkflowExecutionStatus queue projection', () => { }) }) }) + +describe('getWorkflowExecutionStatus settled resume attempts', () => { + const resumeInput = { ...input, executionId: 'resume-run-1' } + + function parentLog(overrides: Record = {}) { + return { + executionId: 'execution-1', + workflowId: 'workflow-1', + workspaceId: 'workspace-1', + status: 'completed', + level: 'info', + trigger: 'api', + startedAt: new Date('2026-08-05T11:00:00.000Z'), + endedAt: new Date('2026-08-05T12:00:05.000Z'), + totalDurationMs: 3605000, + executionData: { finalOutput: { answer: 42 } }, + costTotal: '0.5', + ...overrides, + } + } + + function settledAttempt(overrides: Record = {}) { + return { + id: 'resume-entry-1', + parentExecutionId: 'execution-1', + status: 'completed', + queuedAt: new Date('2026-08-05T12:00:00.000Z'), + claimedAt: new Date('2026-08-05T12:00:01.000Z'), + completedAt: new Date('2026-08-05T12:00:05.000Z'), + failureReason: null, + ...overrides, + } + } + + /** The attempt has no log of its own; the parent run is read second. */ + function queueSettledResume( + attempt: Record, + log: Record, + pausedRows: unknown[] = [] + ) { + queueTableRows(schemaMock.workflowExecutionLogs, []) + queueTableRows(schemaMock.resumeQueue, [attempt]) + queueTableRows(schemaMock.workflowExecutionLogs, [log]) + queueTableRows(schemaMock.resumeQueue, []) + queueTableRows(schemaMock.pausedExecutions, pausedRows) + } + + beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + mockGetJob.mockResolvedValue(null) + mockMaterializeForDisplayWithBlockOutputs.mockImplementation(async (executionData) => ({ + executionData, + blockOutputs: new Map(), + })) + }) + + it('projects a completed resume from the run it continued, under its own run ID', async () => { + queueSettledResume(settledAttempt(), parentLog()) + + const status = await getWorkflowExecutionStatus({ ...resumeInput, includeOutput: true }) + + expect(status).toEqual({ + executionId: 'resume-run-1', + workflowId: 'workflow-1', + status: 'completed', + trigger: 'api', + level: 'info', + startedAt: '2026-08-05T12:00:01.000Z', + endedAt: '2026-08-05T12:00:05.000Z', + totalDurationMs: 4000, + paused: null, + cost: { total: 0.5 }, + error: null, + finalOutput: { answer: 42 }, + blockOutputs: null, + }) + expect(mockMaterializeForDisplayWithBlockOutputs).toHaveBeenCalledWith( + expect.anything(), + { workspaceId: 'workspace-1', workflowId: 'workflow-1', executionId: 'execution-1' }, + [] + ) + expect(mockGetJob).toHaveBeenCalledWith('workflow-execution:resume-run-1') + }) + + it('reports the next pause when a completed resume paused the run again', async () => { + queueSettledResume(settledAttempt(), parentLog({ status: 'paused', executionData: {} }), [ + { + id: 'paused-1', + status: 'partially_resumed', + pausePoints: { + 'context-2': { + contextId: 'context-2', + blockId: 'block-2', + pauseKind: 'human', + resumeStatus: 'paused', + }, + }, + metadata: {}, + resumedCount: 1, + pausedAt: new Date('2026-08-05T12:00:04.000Z'), + nextResumeAt: null, + }, + ]) + + const status = await getWorkflowExecutionStatus(resumeInput) + + expect(status).toMatchObject({ + executionId: 'resume-run-1', + status: 'paused', + endedAt: '2026-08-05T12:00:05.000Z', + paused: { contextId: 'context-2', pausedExecutionId: 'paused-1', resumedCount: 1 }, + }) + }) + + it('reports no end time while a later resume is still running the run', async () => { + queueSettledResume(settledAttempt(), parentLog({ status: 'running', endedAt: null })) + + const status = await getWorkflowExecutionStatus(resumeInput) + + expect(status).toMatchObject({ + executionId: 'resume-run-1', + status: 'running', + startedAt: '2026-08-05T12:00:01.000Z', + endedAt: null, + totalDurationMs: null, + }) + }) + + it('reports a failed resume that left the run paused as failed with its reason', async () => { + queueSettledResume( + settledAttempt({ status: 'failed', failureReason: 'Resume execution cancelled' }), + parentLog({ status: 'paused', executionData: {} }) + ) + + const status = await getWorkflowExecutionStatus({ ...resumeInput, includeOutput: true }) + + expect(status).toMatchObject({ + executionId: 'resume-run-1', + status: 'failed', + level: 'error', + error: 'Resume execution cancelled', + endedAt: '2026-08-05T12:00:05.000Z', + paused: null, + finalOutput: null, + blockOutputs: null, + }) + }) + + it("prefers the run's own error when the failed resume failed the run", async () => { + queueSettledResume( + settledAttempt({ status: 'failed', failureReason: 'Unexpected error' }), + parentLog({ status: 'failed', level: 'error', executionData: { error: 'Block 2 timed out' } }) + ) + + const status = await getWorkflowExecutionStatus(resumeInput) + + expect(status).toMatchObject({ status: 'failed', error: 'Block 2 timed out' }) + }) + + it('reports a resume that lost to cancellation as cancelled', async () => { + queueSettledResume( + settledAttempt({ status: 'failed', failureReason: 'Paused execution cancelled' }), + parentLog({ status: 'cancelled', executionData: {} }) + ) + + const status = await getWorkflowExecutionStatus(resumeInput) + + expect(status).toMatchObject({ status: 'cancelled', level: 'info', error: null }) + }) + + it('returns null when the run a settled resume continued no longer exists', async () => { + queueTableRows(schemaMock.workflowExecutionLogs, []) + queueTableRows(schemaMock.resumeQueue, [settledAttempt()]) + queueTableRows(schemaMock.workflowExecutionLogs, []) + queueTableRows(schemaMock.resumeQueue, []) + + await expect(getWorkflowExecutionStatus(resumeInput)).resolves.toBeNull() + }) +}) diff --git a/apps/sim/lib/workflows/executor/execution-status.ts b/apps/sim/lib/workflows/executor/execution-status.ts index 873dc5182ed..edac4c8649f 100644 --- a/apps/sim/lib/workflows/executor/execution-status.ts +++ b/apps/sim/lib/workflows/executor/execution-status.ts @@ -1,6 +1,6 @@ import { db } from '@sim/db' import { pausedExecutions, resumeQueue, workflowExecutionLogs } from '@sim/db/schema' -import { and, eq, inArray, sql } from 'drizzle-orm' +import { and, eq, sql } from 'drizzle-orm' import type { WorkflowExecutionStatusResponse } from '@/lib/api/contracts/workflows' import { getJobQueue } from '@/lib/core/async-jobs' import type { Job } from '@/lib/core/async-jobs/types' @@ -120,6 +120,74 @@ function projectQueueJob( } } +interface ResumeAttemptRow { + id: string + parentExecutionId: string + status: string + queuedAt: Date + claimedAt: Date | null + completedAt: Date | null + failureReason: string | null +} + +type SettledResumeAttemptRow = ResumeAttemptRow & { status: 'completed' | 'failed' } + +function isSettledResumeAttempt( + attempt: ResumeAttemptRow | undefined +): attempt is SettledResumeAttemptRow { + return attempt?.status === 'completed' || attempt?.status === 'failed' +} + +/** + * Projects a finished resume attempt as its own run resource. + * + * A resume never writes a log row of its own: it continues the paused run and + * records under the parent's execution ID, so once its queue entry settles the + * run it continued is the only durable record of what it did. The attempt + * keeps its own ID and timings, and borrows the rest from that run. + * + * A `completed` attempt is one whose segment ran to its end — the workflow + * finished, failed, or paused again — so the run's state is the answer, + * including when a later resume has since moved the run on (which is why an + * active run reports no end time). A `failed` attempt never finished its + * segment and may have left the run paused for another attempt; it reads as + * failed with its recorded reason unless the run itself was cancelled or failed + * with a more specific error. + */ +function projectSettledResumeAttempt( + executionId: string, + attempt: SettledResumeAttemptRow, + run: WorkflowExecutionStatusResponse +): WorkflowExecutionStatusResponse { + const startedAt = attempt.claimedAt ?? attempt.queuedAt + const continuesInRun = + attempt.status === 'completed' && (run.status === 'queued' || run.status === 'running') + const endedAt = continuesInRun ? null : attempt.completedAt + const resource: WorkflowExecutionStatusResponse = { + ...run, + executionId, + startedAt: startedAt.toISOString(), + endedAt: endedAt?.toISOString() ?? null, + totalDurationMs: endedAt ? Math.max(0, endedAt.getTime() - startedAt.getTime()) : null, + } + if (attempt.status === 'completed') return resource + + const cancelled = run.status === 'cancelled' + return { + ...resource, + status: cancelled ? 'cancelled' : 'failed', + level: cancelled ? 'info' : 'error', + paused: null, + error: cancelled + ? null + : ((run.status === 'failed' ? run.error : null) ?? + attempt.failureReason ?? + 'Resume execution failed'), + finalOutput: null, + blockOutputs: null, + } +} + export interface GetWorkflowExecutionStatusInput { workflowId: string executionId: string @@ -246,25 +314,29 @@ async function readWorkflowExecutionStatus( ) .limit(1) - const [activeResume] = await db + const [resumeAttempt] = await db .select({ id: resumeQueue.id, + parentExecutionId: resumeQueue.parentExecutionId, status: resumeQueue.status, queuedAt: resumeQueue.queuedAt, claimedAt: resumeQueue.claimedAt, + completedAt: resumeQueue.completedAt, + failureReason: resumeQueue.failureReason, }) .from(resumeQueue) .innerJoin(pausedExecutions, eq(resumeQueue.pausedExecutionId, pausedExecutions.id)) .where( - and( - eq(resumeQueue.newExecutionId, executionId), - eq(pausedExecutions.workflowId, workflowId), - inArray(resumeQueue.status, ['pending', 'claimed'] as const) - ) + and(eq(resumeQueue.newExecutionId, executionId), eq(pausedExecutions.workflowId, workflowId)) ) - .orderBy(sql`case when ${resumeQueue.status} = 'claimed' then 0 else 1 end`) + .orderBy(sql`case ${resumeQueue.status} when 'claimed' then 0 when 'pending' then 1 else 2 end`) .limit(1) + const activeResume = + resumeAttempt?.status === 'pending' || resumeAttempt?.status === 'claimed' + ? resumeAttempt + : undefined + const hasTerminalLog = logRow?.status === 'completed' || logRow?.status === 'failed' || logRow?.status === 'cancelled' const projectedResume = hasTerminalLog ? undefined : activeResume @@ -304,7 +376,14 @@ async function readWorkflowExecutionStatus( } } - if (!logRow) return null + if (!logRow) { + if (!isSettledResumeAttempt(resumeAttempt)) return null + const run = await readWorkflowExecutionStatus({ + ...input, + executionId: resumeAttempt.parentExecutionId, + }) + return run ? projectSettledResumeAttempt(executionId, resumeAttempt, run) : null + } const [pausedRow] = await db .select({ From 294de5fcd89a7cc75bce245a1575718efb8eb046 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 16 Sep 2026 15:40:06 -0700 Subject: [PATCH 25/43] chore(auth): diagnose unexpected managed OAuth callback failures (#7896) --- .../credential-groups/oauth-callback.test.ts | 86 ++++++++++++++++++- .../api/credential-groups/oauth-callback.ts | 52 ++++++++--- 2 files changed, 126 insertions(+), 12 deletions(-) diff --git a/apps/sim/app/api/credential-groups/oauth-callback.test.ts b/apps/sim/app/api/credential-groups/oauth-callback.test.ts index ba64bcf7698..c132e570485 100644 --- a/apps/sim/app/api/credential-groups/oauth-callback.test.ts +++ b/apps/sim/app/api/credential-groups/oauth-callback.test.ts @@ -1,4 +1,5 @@ /** @vitest-environment node */ +import { sha256Hex } from '@sim/security/hash' import { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' import { OrchestrationError } from '@/lib/core/orchestration/types' @@ -11,6 +12,7 @@ const mocks = vi.hoisted(() => ({ consumeAttempt: vi.fn(), logError: vi.fn(), completeSetupOAuth: vi.fn(), + authenticateSession: vi.fn(), })) vi.mock('@sim/logger', () => ({ @@ -21,7 +23,7 @@ vi.mock('@/lib/knowledge/application/github-setup', () => ({ })) vi.mock('@/lib/api/server/routes', () => ({ internalSessionAuth: { - authenticate: async () => ({ kind: 'session', userId: 'admin', sessionId: 'browser' }), + authenticate: mocks.authenticateSession, }, })) vi.mock('@/lib/core/utils/urls', () => ({ getBaseUrl: () => 'https://sim.test' })) @@ -127,6 +129,55 @@ describe('GitHub managed OAuth failure presentation', () => { provider: 'github-repositories', failure: 'failed', errorClass: 'unexpected', + stage: 'enrollment_completion', + errorType: 'Error', + fingerprint: sha256Hex('member@example.com ghu_token').slice(0, 12), + }) + }) + + it('identifies a wrapped database failure without logging SQL, parameters, or provider data', async () => { + const cause = Object.assign(new Error('duplicate key for member@example.com'), { + name: 'PostgresError', + code: '23505', + detail: 'ghu_private_token', + }) + mocks.consumeAttempt.mockResolvedValue(attempt) + mocks.completeOAuth.mockRejectedValueOnce( + new Error('Failed query: INSERT INTO credential\nparams: ghu_private_token', { cause }) + ) + const response = await completeCallback() + expect(response.headers.get('location')).toContain('oauth=failed') + expect(mocks.logError).toHaveBeenCalledExactlyOnceWith('Managed OAuth authorization failed', { + provider: 'github-repositories', + failure: 'failed', + errorClass: 'unexpected', + stage: 'enrollment_completion', + errorType: 'PostgresError', + databaseCode: '23505', + fingerprint: sha256Hex(cause.message).slice(0, 12), + }) + const logged = JSON.stringify(mocks.logError.mock.calls) + expect(logged).not.toContain('member@example.com') + expect(logged).not.toContain('ghu_private_token') + expect(logged).not.toContain('INSERT') + }) + + it('does not log arbitrary error names or codes as diagnostic metadata', async () => { + mocks.consumeAttempt.mockResolvedValue(attempt) + mocks.completeOAuth.mockRejectedValueOnce( + Object.assign(new Error('private provider response'), { + name: 'ghu_private_token', + code: 'client_secret=private', + }) + ) + await completeCallback() + expect(mocks.logError).toHaveBeenCalledExactlyOnceWith('Managed OAuth authorization failed', { + provider: 'github-repositories', + failure: 'failed', + errorClass: 'unexpected', + stage: 'enrollment_completion', + errorType: 'UnknownError', + fingerprint: sha256Hex('private provider response').slice(0, 12), }) }) @@ -149,7 +200,40 @@ describe('GitHub managed OAuth failure presentation', () => { describe('GitHub installation setup OAuth return target', () => { beforeEach(() => { vi.clearAllMocks() + mocks.authenticateSession.mockResolvedValue({ + kind: 'session', + userId: 'admin', + sessionId: 'browser', + }) }) + + it.each(['session_authentication', 'setup_completion'])( + 'identifies an unexpected failure during %s without exposing its message', + async (stage) => { + mocks.consumeAttempt.mockResolvedValue({ + ...attempt, + returnTo: 'github-installation', + organizationId: 'organization', + completionId, + }) + const error = new TypeError('private callback data') + if (stage === 'session_authentication') { + mocks.authenticateSession.mockRejectedValueOnce(error) + } else { + mocks.completeSetupOAuth.mockRejectedValueOnce(error) + } + const response = await completeCallback() + expect(response.headers.get('location')).toContain('oauth=failed') + expect(mocks.logError).toHaveBeenCalledExactlyOnceWith('Managed OAuth authorization failed', { + provider: 'github-repositories', + failure: 'failed', + errorClass: 'unexpected', + stage, + errorType: 'TypeError', + fingerprint: sha256Hex(error.message).slice(0, 12), + }) + } + ) it('resumes only the server-owned setup after the guarded OAuth completion', async () => { mocks.consumeAttempt.mockResolvedValue({ ...attempt, diff --git a/apps/sim/app/api/credential-groups/oauth-callback.ts b/apps/sim/app/api/credential-groups/oauth-callback.ts index 5808df6c42d..0d8b0240ac2 100644 --- a/apps/sim/app/api/credential-groups/oauth-callback.ts +++ b/apps/sim/app/api/credential-groups/oauth-callback.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' -import { getErrorMessage } from '@sim/utils/errors' +import { sha256Hex } from '@sim/security/hash' +import { describeError, getErrorMessage } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import type { CredentialGroupOAuthCallbackQuery } from '@/lib/api/contracts/credential-groups' import { internalSessionAuth } from '@/lib/api/server/routes' @@ -23,6 +24,18 @@ import { } from '@/app/api/credential-groups/enrollment-redirect' const logger = createLogger('CredentialGroupOAuthCallbackAPI') +const DIAGNOSTIC_ERROR_TYPES = new Set([ + 'Error', + 'TypeError', + 'ReferenceError', + 'SyntaxError', + 'RangeError', + 'ZodError', + 'PostgresError', + 'DrizzleQueryError', + 'InternalUnauthenticatedError', + 'ManagedOAuthCredentialError', +]) interface HandleCredentialGroupOAuthCallbackParams { request: NextRequest @@ -87,13 +100,17 @@ export async function handleCredentialGroupOAuthCallback({ return failureRedirect('failed') } + let stage = 'session_authentication' try { if (installationSetup) { const principal = await internalSessionAuth.authenticate() + stage = 'setup_completion' await completeGitHubSetupReaderOAuth.execute({ principal, input: { attempt, code }, request }) return setupRedirect() } + stage = 'enrollment_authentication' const principal = await credentialGroupOAuthAttemptPrincipal(attempt) + stage = 'enrollment_completion' await completePublicCredentialGroupOAuth.execute({ principal, input: { attempt, code }, @@ -137,19 +154,32 @@ export async function handleCredentialGroupOAuthCallback({ } } const applicationError = asOrchestrationError(error) + const errorClass = + error instanceof CredentialGroupInvitationUnavailableError + ? 'invitation_unavailable' + : error instanceof CredentialGroupOAuthError + ? 'credential_group_oauth' + : error instanceof CredentialGroupProviderConfigurationError + ? 'provider_configuration' + : applicationError + ? 'application' + : 'unexpected' + const unexpectedError = errorClass === 'unexpected' ? describeError(error) : undefined logger.error('Managed OAuth authorization failed', { provider, failure: status, - errorClass: - error instanceof CredentialGroupInvitationUnavailableError - ? 'invitation_unavailable' - : error instanceof CredentialGroupOAuthError - ? 'credential_group_oauth' - : error instanceof CredentialGroupProviderConfigurationError - ? 'provider_configuration' - : applicationError - ? 'application' - : 'unexpected', + errorClass, + /** Provider errors and SQL parameters may contain credentials; retain only bounded diagnostics. */ + ...(unexpectedError && { + stage, + errorType: DIAGNOSTIC_ERROR_TYPES.has(unexpectedError.name) + ? unexpectedError.name + : 'UnknownError', + fingerprint: sha256Hex(unexpectedError.message).slice(0, 12), + ...(unexpectedError.code && /^[0-9A-Z]{5}$/.test(unexpectedError.code) + ? { databaseCode: unexpectedError.code } + : {}), + }), ...(error instanceof CredentialGroupOAuthError && { statusCode: error.statusCode }), ...(error instanceof CredentialGroupProviderConfigurationError && { statusCode: 503 }), ...(applicationError && { From 664ca8267634a2b01925c1f9342bc89c80fef245 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 16 Sep 2026 15:48:47 -0700 Subject: [PATCH 26/43] fix(search): preserve Google sync diagnostics and retry transient failures (#7895) * fix(search): preserve Google sync diagnostics and retry transient failures * fix(search): sanitize in-process database failure logs --- apps/docs/content/docs/search/gmail.mdx | 2 + .../content/docs/search/google-calendar.mdx | 2 + .../docs/content/docs/search/google-drive.mdx | 2 + .../background/knowledge-processing.test.ts | 17 ++ apps/sim/background/knowledge-processing.ts | 8 +- .../connectors/gmail/company-crawl.test.ts | 12 +- apps/sim/connectors/gmail/gmail.test.ts | 18 ++- apps/sim/connectors/gmail/gmail.ts | 132 ++++++--------- .../google-calendar/google-calendar.ts | 98 ++++++------ .../connectors/google-drive/directory.test.ts | 19 +++ apps/sim/connectors/google-drive/directory.ts | 43 +++-- .../google-drive/google-drive-errors.ts | 88 +++------- .../google-drive/google-drive.test.ts | 6 +- .../connectors/google-drive/google-drive.ts | 90 +++++++---- .../google-drive/workspace-drives.ts | 4 +- .../google-workspace/api-errors.test.ts | 147 +++++++++++++++++ .../connectors/google-workspace/api-errors.ts | 150 ++++++++++++++++++ apps/sim/connectors/google-workspace/users.ts | 6 +- apps/sim/connectors/source-error.ts | 6 +- .../connectors/connector-error.test.ts | 29 ++++ .../knowledge/connectors/connector-error.ts | 40 ++++- .../connectors/external-group-sync.test.ts | 6 + .../connectors/external-group-sync.ts | 5 +- .../document-processing-source.test.ts | 81 ++++++++++ apps/sim/lib/knowledge/documents/service.ts | 11 +- 25 files changed, 756 insertions(+), 266 deletions(-) create mode 100644 apps/sim/connectors/google-workspace/api-errors.test.ts create mode 100644 apps/sim/connectors/google-workspace/api-errors.ts diff --git a/apps/docs/content/docs/search/gmail.mdx b/apps/docs/content/docs/search/gmail.mdx index e5c3f63adbe..0d86d7a59e9 100644 --- a/apps/docs/content/docs/search/gmail.mdx +++ b/apps/docs/content/docs/search/gmail.mdx @@ -139,6 +139,8 @@ Updates, removals, and access refresh in the background. Empty mailboxes and fil ## Troubleshooting +An individual thread failure does not mean the whole mailbox failed. Sim retries temporary server and rate-limit errors with bounded backoff. Error diagnostics record the Google API operation, HTTP status, and a recognized error reason when available. + | What you see | What to do | | --- | --- | | A different email is requested | Use the Google account matching your verified Sim email. A separate personal account or alias does not satisfy the match. | diff --git a/apps/docs/content/docs/search/google-calendar.mdx b/apps/docs/content/docs/search/google-calendar.mdx index 5ce4684134c..a2317d9d941 100644 --- a/apps/docs/content/docs/search/google-calendar.mdx +++ b/apps/docs/content/docs/search/google-calendar.mdx @@ -135,6 +135,8 @@ Search schedules syncs hourly. Event edits, cancellations, access changes, inact ## Troubleshooting +When a sync fails, **Sync history** includes the Google API operation, HTTP status, and a recognized reason when available. A `403` alone does not establish a missing scope: `rateLimitExceeded` and `userRateLimitExceeded` are retried with bounded backoff. A persistent access error requires checking the affected user’s Calendar access. + | What you see | What to do | | --- | --- | | No events | Check the date range, search query, and calendar IDs. Use an empty calendar selection or `primary` for each person's own calendar. | diff --git a/apps/docs/content/docs/search/google-drive.mdx b/apps/docs/content/docs/search/google-drive.mdx index e647b5544c8..3c70e3ccb62 100644 --- a/apps/docs/content/docs/search/google-drive.mdx +++ b/apps/docs/content/docs/search/google-drive.mdx @@ -155,6 +155,8 @@ Search schedules syncs hourly. Central crawls revisit the selected users' files ## Troubleshooting +**Directory permission sync failed** means Sim could not fully verify group membership. Check the Directory administrator’s access to the affected group and any nested groups; this is separate from file-download access. An incomplete membership read does not replace the last verified membership, which remains subject to freshness checks. + | Problem | Next step | | --- | --- | | Google rejects authorization (`unauthorized_client`) | In **Manage Domain Wide Delegation**, verify the numeric **Client ID** matches `client_id` in the JSON key uploaded to Sim and all required scopes appear under **View details**. Check pending approval and allow time for recent changes to propagate. Changing the OAuth consent screen alone does not authorize delegation. | diff --git a/apps/sim/background/knowledge-processing.test.ts b/apps/sim/background/knowledge-processing.test.ts index 6fda5c229bb..64f5f7c556f 100644 --- a/apps/sim/background/knowledge-processing.test.ts +++ b/apps/sim/background/knowledge-processing.test.ts @@ -1,6 +1,7 @@ /** * @vitest-environment node */ +import { DrizzleQueryError } from 'drizzle-orm/errors' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -566,6 +567,22 @@ describe('knowledge processing worker', () => { expect(mockTrigger).not.toHaveBeenCalled() }) + it('keeps database failures retryable without sending SQL or parameters to Trigger', async () => { + const error = new DrizzleQueryError( + 'insert private SQL', + ['private bound content'], + Object.assign(new Error('private database detail'), { code: '57014' }) + ) + mockProcessDocumentAsync.mockRejectedValueOnce(error) + const failure = await runDocumentProcessing(WORKSPACE_PAYLOAD).catch( + (caught: unknown) => caught + ) + expect(failure).toBeInstanceOf(Error) + expect(failure).toMatchObject({ message: 'Database request failed (SQLSTATE 57014).' }) + expect(failure).not.toHaveProperty('cause') + expect(JSON.stringify(failure)).not.toContain('private') + }) + it('retries failed provider continuation dispatch instead of reporting a successful deferral', async () => { const error = new Error('Trigger dispatch unavailable') mockTrigger.mockRejectedValue(error) diff --git a/apps/sim/background/knowledge-processing.ts b/apps/sim/background/knowledge-processing.ts index 4016e7defdd..7981c351208 100644 --- a/apps/sim/background/knowledge-processing.ts +++ b/apps/sim/background/knowledge-processing.ts @@ -7,6 +7,7 @@ import { isBYOKEmbeddingCredentialRejection, isEmbeddingQuotaExhaustion, } from '@/lib/embeddings' +import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' import { getOcrRequestRejection, isPermanentDocumentProcessingError, @@ -194,7 +195,12 @@ export async function runDocumentProcessing( processingTime: Date.now() - startedAt, } } - logger.error(`[${requestId}] Failed to process document: ${docData.filename}`, error) + const diagnostic = getConnectorFailureDiagnostic(error) + logger.error( + `[${requestId}] Failed to process document: ${docData.filename}`, + diagnostic ?? error + ) + if (diagnostic?.category === 'database') throw new Error(diagnostic.message) throw error } } diff --git a/apps/sim/connectors/gmail/company-crawl.test.ts b/apps/sim/connectors/gmail/company-crawl.test.ts index 5030a0f4948..2bf06db0956 100644 --- a/apps/sim/connectors/gmail/company-crawl.test.ts +++ b/apps/sim/connectors/gmail/company-crawl.test.ts @@ -9,8 +9,13 @@ const { fetchProvider, listUsers, getUser } = vi.hoisted(() => ({ getUser: vi.fn(), })) -vi.mock('@/lib/knowledge/documents/utils', () => ({ VALIDATE_RETRY_OPTIONS: {} })) -vi.mock('@/lib/knowledge/documents/secure-fetch.server', () => ({ fetchWithRetry: fetchProvider })) +vi.mock('@/lib/knowledge/documents/secure-fetch.server', () => ({ + fetchWithRetry: ( + url: string, + init: RequestInit, + options?: import('@/lib/knowledge/documents/utils').RetryOptions + ) => (options?.fetcher ? options.fetcher(url, init, fetchProvider) : fetchProvider(url, init)), +})) vi.mock('@/components/icons', () => ({ GmailIcon: () => null })) vi.mock('@/connectors/google-workspace/users', () => ({ GOOGLE_WORKSPACE_USERS_PAGE_SIZE: 100, @@ -180,8 +185,7 @@ describe('company-wide Gmail indexing', () => { ).resolves.toEqual({ valid: true }) expect(fetchProvider).toHaveBeenCalledWith( expect.stringContaining('/profile'), - expect.objectContaining({ signal: controller.signal }), - expect.any(Object) + expect.objectContaining({ signal: controller.signal }) ) }) diff --git a/apps/sim/connectors/gmail/gmail.test.ts b/apps/sim/connectors/gmail/gmail.test.ts index f4d80fd1fd4..58595617115 100644 --- a/apps/sim/connectors/gmail/gmail.test.ts +++ b/apps/sim/connectors/gmail/gmail.test.ts @@ -5,9 +5,17 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { mockFetchWithRetry } = vi.hoisted(() => ({ mockFetchWithRetry: vi.fn() })) -vi.mock('@/lib/knowledge/documents/utils', () => ({ VALIDATE_RETRY_OPTIONS: {} })) vi.mock('@/lib/knowledge/documents/secure-fetch.server', () => ({ - fetchWithRetry: mockFetchWithRetry, + fetchWithRetry: ( + url: string, + init: RequestInit, + options: { + fetcher?: (url: string, init: RequestInit, transport: typeof fetch) => Promise + } + ) => + options.fetcher + ? options.fetcher(url, init, mockFetchWithRetry) + : mockFetchWithRetry(url, init), })) vi.mock('@/components/icons', () => ({ GmailIcon: () => null })) vi.mock('@/lib/knowledge/documents/service', () => ({ @@ -618,7 +626,9 @@ describe('Gmail separately stored message bodies', () => { .catch((caught: unknown) => caught) expect(error).toBeInstanceOf(Error) - expect(error).toMatchObject({ message: `Failed to fetch Gmail message body: ${status}` }) + expect(error).toMatchObject({ + message: `gmail.messages.attachments.get failed (HTTP ${status}).`, + }) expect(gmailConnector.isCredentialInvalidError?.(error)).toBe(status === 401) } ) @@ -899,7 +909,7 @@ describe('Gmail thread revisions and deferred content', () => { .mockResolvedValueOnce(Response.json({ threads: [{ id: 'thread-1' }] })) .mockResolvedValueOnce(new Response(null, { status })) await expect(gmailConnector.listDocuments('token', {}, undefined, {})).rejects.toThrow( - `Failed to fetch thread thread-1: ${status}` + `gmail.threads.get failed (HTTP ${status}).` ) } ) diff --git a/apps/sim/connectors/gmail/gmail.ts b/apps/sim/connectors/gmail/gmail.ts index f63ea781624..4f2be5cdde1 100644 --- a/apps/sim/connectors/gmail/gmail.ts +++ b/apps/sim/connectors/gmail/gmail.ts @@ -3,9 +3,9 @@ import { getErrorMessage, toError } from '@sim/utils/errors' import { isPlainRecord } from '@sim/utils/object' import { mapWithConcurrency } from '@/lib/core/utils/concurrency' import { isPayloadSizeLimitError, readResponseJsonWithLimit } from '@/lib/core/utils/stream-limits' -import { fetchWithRetry } from '@/lib/knowledge/documents/secure-fetch.server' import { VALIDATE_RETRY_OPTIONS } from '@/lib/knowledge/documents/utils' import { DEFAULT_MAX_THREADS, gmailConnectorMeta } from '@/connectors/gmail/meta' +import { fetchGoogleApiWithRetry, GoogleApiError } from '@/connectors/google-workspace/api-errors' import { getGoogleWorkspaceDocument, InvalidGoogleWorkspaceCursor, @@ -53,16 +53,6 @@ const CHANGED_THREAD_CONCURRENCY = 5 /** Gmail's thread listing omits these unless `includeSpamTrash` is set; the feed must agree. */ const HIDDEN_LABEL_IDS = new Set(['SPAM', 'TRASH']) -class GmailApiError extends Error { - constructor( - message: string, - readonly status: number - ) { - super(`${message}: ${status}`) - this.name = 'GmailApiError' - } -} - interface GmailHeader { name: string value: string @@ -229,26 +219,23 @@ async function getLabelIndex( let index: GmailLabelIndex | null = null try { - const response = await fetchWithRetry(`${GMAIL_API_BASE}/labels`, { - method: 'GET', - signal, - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', - }, - }) + const response = await fetchGoogleApiWithRetry( + 'gmail.labels.list', + `${GMAIL_API_BASE}/labels`, + { + method: 'GET', + signal, + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: 'application/json', + }, + } + ) - if (response.status === 401) { - throw new GmailApiError('Failed to fetch Gmail labels', response.status) - } - if (response.ok) { - index = buildLabelIndex(await readLabels(response)) - } else { - logger.warn('Failed to fetch Gmail labels', { status: response.status }) - } + index = buildLabelIndex(await readLabels(response)) } catch (error) { signal?.throwIfAborted() - if (error instanceof GmailApiError && error.status === 401) throw error + if (error instanceof GoogleApiError && error.status === 401) throw error logger.warn('Failed to fetch Gmail labels', { error: toError(error).message }) } @@ -387,7 +374,8 @@ async function readMessageBody( if (!body.attachmentId) return body.data ? decodeBase64Url(body.data, context) : '' const params = new URLSearchParams({ fields: 'data,size' }) - const response = await fetchWithRetry( + const response = await fetchGoogleApiWithRetry( + 'gmail.messages.attachments.get', `${GMAIL_API_BASE}/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(body.attachmentId)}?${params}`, { method: 'GET', @@ -395,9 +383,6 @@ async function readMessageBody( headers: { Authorization: `Bearer ${context.accessToken}`, Accept: 'application/json' }, } ) - if (!response.ok) { - throw new GmailApiError('Failed to fetch Gmail message body', response.status) - } let fetchedBody: unknown try { @@ -591,18 +576,16 @@ async function fetchThread( params.set('fields', 'id,historyId,snippet,messages(id,labelIds,internalDate)') const url = `${GMAIL_API_BASE}/threads/${encodeURIComponent(threadId)}?${params}` - const response = await fetchWithRetry(url, { - method: 'GET', - signal, - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', - }, - }) - - if (!response.ok) { - if (response.status === 404) return null - throw new GmailApiError(`Failed to fetch thread ${threadId}`, response.status) + let response: Response + try { + response = await fetchGoogleApiWithRetry('gmail.threads.get', url, { + method: 'GET', + signal, + headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, + }) + } catch (error) { + if (error instanceof GoogleApiError && error.status === 404) return null + throw error } const thread = await readResponseJsonWithLimit(response, { @@ -790,18 +773,21 @@ function threadInScope(thread: GmailThread, scope: GmailChangeScope): boolean { const gmailMailboxConnector: ConnectorConfig = { ...gmailConnectorMeta, - isCredentialInvalidError: (error) => error instanceof GmailApiError && error.status === 401, + isCredentialInvalidError: (error) => error instanceof GoogleApiError && error.status === 401, /** The mailbox's current history id; `users.history.list` replays everything after it. */ getChangeCursor: async (accessToken, _sourceConfig, syncContext): Promise => { if (syncContext?.mirrorsSourceAcls === true) { throw new Error('Company-wide Gmail indexing uses complete mailbox listings') } - const response = await fetchWithRetry(`${GMAIL_API_BASE}/profile?fields=historyId`, { - method: 'GET', - headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, - }) - if (!response.ok) throw new GmailApiError('Failed to read the Gmail profile', response.status) + const response = await fetchGoogleApiWithRetry( + 'gmail.users.getProfile', + `${GMAIL_API_BASE}/profile?fields=historyId`, + { + method: 'GET', + headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, + } + ) const data: unknown = await response.json() if ( !isPlainRecord(data) || @@ -851,14 +837,14 @@ const gmailMailboxConnector: ConnectorConfig = { for (const type of HISTORY_TYPES) params.append('historyTypes', type) if (pageToken) params.set('pageToken', pageToken) - const response = await fetchWithRetry(`${GMAIL_API_BASE}/history?${params.toString()}`, { - method: 'GET', - headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, - }) - if (!response.ok) { - logger.warn('Failed to list Gmail history', { status: response.status }) - throw new GmailApiError('Failed to list Gmail history', response.status) - } + const response = await fetchGoogleApiWithRetry( + 'gmail.history.list', + `${GMAIL_API_BASE}/history?${params.toString()}`, + { + method: 'GET', + headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, + } + ) const page = parseHistoryList(await response.json()) const changes = await mapWithConcurrency( @@ -881,7 +867,7 @@ const gmailMailboxConnector: ConnectorConfig = { /** Gmail answers 404 once `startHistoryId` falls outside the history it retains. */ isChangeCursorInvalidError: (error) => error instanceof InvalidGmailChangeCursorError || - (error instanceof GmailApiError && error.status === 404), + (error instanceof GoogleApiError && error.status === 404), listDocuments: async ( accessToken: string, @@ -953,7 +939,7 @@ const gmailMailboxConnector: ConnectorConfig = { maxThreads, }) - const response = await fetchWithRetry(url, { + const response = await fetchGoogleApiWithRetry('gmail.threads.list', url, { method: 'GET', signal, headers: { @@ -962,11 +948,6 @@ const gmailMailboxConnector: ConnectorConfig = { }, }) - if (!response.ok) { - logger.error('Failed to list Gmail threads', { status: response.status }) - throw new GmailApiError('Failed to list Gmail threads', response.status) - } - /** Gmail can return 204 when an empty listing has no requested metadata fields. */ const { threads, nextPageToken } = parseThreadList( response.status === 204 @@ -1102,7 +1083,8 @@ const gmailMailboxConnector: ConnectorConfig = { try { const profileUrl = `${GMAIL_API_BASE}/profile` - const profileResponse = await fetchWithRetry( + await fetchGoogleApiWithRetry( + 'gmail.users.getProfile', profileUrl, { method: 'GET', @@ -1115,10 +1097,6 @@ const gmailMailboxConnector: ConnectorConfig = { VALIDATE_RETRY_OPTIONS ) - if (!profileResponse.ok) { - return { valid: false, error: `Failed to access Gmail: ${profileResponse.status}` } - } - /** * Labels may arrive as ids (from the `gmail.labels` selector) or as names * (typed into the advanced input), so both forms are accepted here and the @@ -1128,7 +1106,8 @@ const gmailMailboxConnector: ConnectorConfig = { let labelIndex = EMPTY_LABEL_INDEX if (configuredLabels.length > 0) { const labelsUrl = `${GMAIL_API_BASE}/labels` - const labelsResponse = await fetchWithRetry( + const labelsResponse = await fetchGoogleApiWithRetry( + 'gmail.labels.list', labelsUrl, { method: 'GET', @@ -1141,10 +1120,6 @@ const gmailMailboxConnector: ConnectorConfig = { VALIDATE_RETRY_OPTIONS ) - if (!labelsResponse.ok) { - return { valid: false, error: 'Failed to fetch labels' } - } - const labels = await readLabels(labelsResponse) labelIndex = buildLabelIndex(labels) const missing = configuredLabels.filter( @@ -1171,7 +1146,8 @@ const gmailMailboxConnector: ConnectorConfig = { if (query?.trim()) { const searchQuery = buildSearchQuery(sourceConfig, labelIndex) const testUrl = `${GMAIL_API_BASE}/threads?q=${encodeURIComponent(searchQuery)}&maxResults=1` - const testResponse = await fetchWithRetry( + await fetchGoogleApiWithRetry( + 'gmail.threads.list', testUrl, { method: 'GET', @@ -1183,10 +1159,6 @@ const gmailMailboxConnector: ConnectorConfig = { }, VALIDATE_RETRY_OPTIONS ) - - if (!testResponse.ok) { - return { valid: false, error: 'Invalid search query. Check Gmail search syntax.' } - } } return { valid: true } diff --git a/apps/sim/connectors/google-calendar/google-calendar.ts b/apps/sim/connectors/google-calendar/google-calendar.ts index 52405ccb028..9847525d292 100644 --- a/apps/sim/connectors/google-calendar/google-calendar.ts +++ b/apps/sim/connectors/google-calendar/google-calendar.ts @@ -1,9 +1,9 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { z } from 'zod' -import { fetchWithRetry } from '@/lib/knowledge/documents/secure-fetch.server' import { VALIDATE_RETRY_OPTIONS } from '@/lib/knowledge/documents/utils' import { DEFAULT_MAX_EVENTS, googleCalendarConnectorMeta } from '@/connectors/google-calendar/meta' +import { fetchGoogleApiWithRetry, GoogleApiError } from '@/connectors/google-workspace/api-errors' import { getGoogleWorkspaceDocument, InvalidGoogleWorkspaceCursor, @@ -35,8 +35,6 @@ const CALENDAR_PAGE_MAX_BYTES = 16 * 1024 * 1024 const EVENT_FIELDS = 'id,status,htmlLink,created,updated,summary,description,location,creator(email,displayName),organizer(email,displayName,self),start(date,dateTime,timeZone),end(date,dateTime,timeZone),attendees(email,displayName,responseStatus,self,resource,optional),recurringEventId,eventType' -class GoogleCalendarCredentialInvalidError extends Error {} - const calendarEventTimeSchema = z.object({ date: z.string().optional(), dateTime: z.string().optional(), @@ -403,7 +401,7 @@ const userCalendarConnector: ConnectorConfig = { ...googleCalendarConnectorMeta, isListingScopeUnavailableError: isListingScopeUnavailableError, - isCredentialInvalidError: (error) => error instanceof GoogleCalendarCredentialInvalidError, + isCredentialInvalidError: (error) => error instanceof GoogleApiError && error.status === 401, listDocuments: async ( accessToken: string, @@ -504,24 +502,24 @@ const userCalendarConnector: ConnectorConfig = { hasPageToken: Boolean(pageToken), }) - const response = await fetchWithRetry(url, { - method: 'GET', - signal: syncContext?.signal instanceof AbortSignal ? syncContext.signal : undefined, - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', - }, - }) - - if (!response.ok) { - if (response.status === 401) { - throw new GoogleCalendarCredentialInvalidError('Reconnect your Google Calendar account') - } + let response: Response + try { + response = await fetchGoogleApiWithRetry('calendar.events.list', url, { + method: 'GET', + signal: syncContext?.signal instanceof AbortSignal ? syncContext.signal : undefined, + headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, + }) + } catch (providerError) { + if (!(providerError instanceof GoogleApiError)) throw providerError logger.error('Failed to list Google Calendar events', { - status: response.status, + status: providerError.status, calendarId, + ...providerError.diagnostic, }) - const error = listingRequestError('Failed to list Google Calendar events', response.status) + const error = + providerError.status === 404 + ? listingRequestError('Failed to list Google Calendar events', providerError.status) + : providerError /** * One of several calendars a member cannot reach is absent from their * listing, not the end of it: move on to the next calendar so the rest of @@ -537,7 +535,7 @@ const userCalendarConnector: ConnectorConfig = { ) { logger.warn('Skipping a Google Calendar the member cannot reach', { calendarId, - status: response.status, + status: providerError.status, }) return calendarIndex + 1 < calendarIds.length ? { @@ -668,21 +666,17 @@ const userCalendarConnector: ConnectorConfig = { const url = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(eventId)}?fields=${encodeURIComponent(EVENT_FIELDS)}` - const response = await fetchWithRetry(url, { - method: 'GET', - signal: syncContext?.signal instanceof AbortSignal ? syncContext.signal : undefined, - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', - }, - }) - - if (!response.ok) { - if (response.status === 404 || response.status === 410) return null - if (response.status === 401) { - throw new GoogleCalendarCredentialInvalidError('Reconnect your Google Calendar account') - } - throw new Error(`Failed to get Google Calendar event: ${response.status}`) + let response: Response + try { + response = await fetchGoogleApiWithRetry('calendar.events.get', url, { + method: 'GET', + signal: syncContext?.signal instanceof AbortSignal ? syncContext.signal : undefined, + headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, + }) + } catch (error) { + if (error instanceof GoogleApiError && (error.status === 404 || error.status === 410)) + return null + throw error } const event = calendarEventSchema.parse(await readCalendarJson(response)) @@ -717,30 +711,28 @@ const userCalendarConnector: ConnectorConfig = { for (const calendarId of calendarIds) { const url = `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events?maxResults=1&singleEvents=true&orderBy=startTime&timeMin=${encodeURIComponent(new Date().toISOString())}` - const response = await fetchWithRetry( - url, - { - method: 'GET', - signal: syncContext?.signal instanceof AbortSignal ? syncContext.signal : undefined, - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', + try { + await fetchGoogleApiWithRetry( + 'calendar.events.list', + url, + { + method: 'GET', + signal: syncContext?.signal instanceof AbortSignal ? syncContext.signal : undefined, + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: 'application/json', + }, }, - }, - VALIDATE_RETRY_OPTIONS - ) - - if (!response.ok) { - if (response.status === 404) { + VALIDATE_RETRY_OPTIONS + ) + } catch (error) { + if (error instanceof GoogleApiError && error.status === 404) { return { valid: false, error: `Calendar not found: ${calendarId}. Check the calendar ID.`, } } - return { - valid: false, - error: `Failed to access Google Calendar "${calendarId}": ${response.status}`, - } + throw error } } diff --git a/apps/sim/connectors/google-drive/directory.test.ts b/apps/sim/connectors/google-drive/directory.test.ts index 58bcc4c55c3..660cd191bf3 100644 --- a/apps/sim/connectors/google-drive/directory.test.ts +++ b/apps/sim/connectors/google-drive/directory.test.ts @@ -215,6 +215,25 @@ describe('the membership a directory reports', () => { await expect(membersOf(GROUP)).rejects.toThrow() }) + it('preserves the denied nested-group operation instead of returning partial membership', async () => { + directory({ 'eng@corp.com': [USER('alice@corp.com'), NESTED('restricted@corp.com')] }) + const healthy = mockFetch.getMockImplementation()! + mockFetch.mockImplementation(async (url: string) => { + if (decodeURIComponent(new URL(url).pathname).includes('/restricted@corp.com/members')) { + return jsonResponse( + { error: { errors: [{ reason: 'forbidden' }], message: 'private detail' } }, + 403 + ) + } + return healthy(url) + }) + + await expect(membersOf(GROUP)).rejects.toMatchObject({ + status: 403, + diagnostic: { operation: 'directory.members.list', reasons: ['forbidden'] }, + }) + }) + /** A directory that hiccups must not cost a group its membership; transient errors are retried. */ it('retries a transient directory error before giving up', async () => { directory({ 'eng@corp.com': [USER('alice@corp.com')] }) diff --git a/apps/sim/connectors/google-drive/directory.ts b/apps/sim/connectors/google-drive/directory.ts index 92b9ae54c54..dd2b9ec1eda 100644 --- a/apps/sim/connectors/google-drive/directory.ts +++ b/apps/sim/connectors/google-drive/directory.ts @@ -65,20 +65,27 @@ export async function validateGoogleDirectoryAccess( throw new Error('Enter a Directory administrator email to mirror Drive permissions.') } - const probe = async (path: string) => + const probe = async (path: string, operation: string) => fetchGoogleDriveWithRetry( `${DIRECTORY_BASE}/${path}`, { headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' } }, - VALIDATE_RETRY_OPTIONS + VALIDATE_RETRY_OPTIONS, + operation ) try { - const groupsResponse = await probe('groups?customer=my_customer&maxResults=1&fields=groups(id)') + const groupsResponse = await probe( + 'groups?customer=my_customer&maxResults=1&fields=groups(id)', + 'directory.groups.list' + ) const groups = (await groupsResponse.json()) as { groups?: { id?: string }[] } - await probe('customer/my_customer/domains?fields=domains(domainName)') + await probe('customer/my_customer/domains?fields=domains(domainName)', 'directory.domains.list') const groupId = groups.groups?.[0]?.id if (groupId) { - await probe(`groups/${encodeURIComponent(groupId)}/members?maxResults=1&fields=members(id)`) + await probe( + `groups/${encodeURIComponent(groupId)}/members?maxResults=1&fields=members(id)`, + 'directory.members.list' + ) } } catch (error) { const guidance = @@ -96,15 +103,20 @@ export async function validateGoogleDirectoryAccess( } } -function directoryFetch(url: string, accessToken: string): Promise { - return fetchGoogleDriveWithRetry(url, { - method: 'GET', - headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, - }) +function directoryFetch(url: string, accessToken: string, operation: string): Promise { + return fetchGoogleDriveWithRetry( + url, + { + method: 'GET', + headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, + }, + {}, + operation + ) } async function getJson(url: string, accessToken: string): Promise { - const response = await directoryFetch(url, accessToken) + const response = await directoryFetch(url, accessToken, 'directory.domains.list') return (await response.json()) as T } @@ -128,7 +140,7 @@ async function listAll( if (pageToken) query.set('pageToken', pageToken) return `${url}?${query.toString()}` }, - fetch: (pageUrl) => directoryFetch(pageUrl, accessToken), + fetch: (pageUrl) => directoryFetch(pageUrl, accessToken, `directory.${itemsKey}.list`), parseError: (response) => response.json().catch(() => null), getItems: (body) => body[itemsKey] as T[] | undefined, getNextPageToken: (body) => body.nextPageToken as string | undefined, @@ -296,6 +308,13 @@ export function openGoogleDirectory( return members } catch (error) { const failure = toError(error) + if (failure instanceof GoogleDriveApiError) { + logger.warn('Failed to read Google group membership', { + groupId, + status: failure.status, + ...failure.diagnostic, + }) + } directMembers.set(groupId, failure) throw failure } diff --git a/apps/sim/connectors/google-drive/google-drive-errors.ts b/apps/sim/connectors/google-drive/google-drive-errors.ts index db9c993b5e5..86a582a68b2 100644 --- a/apps/sim/connectors/google-drive/google-drive-errors.ts +++ b/apps/sim/connectors/google-drive/google-drive-errors.ts @@ -5,13 +5,15 @@ import { resolveRetryDelayMs, retryWithExponentialBackoff, } from '@/lib/knowledge/documents/utils' +import { + readGoogleErrorReasons, + safeGoogleErrorReasons, +} from '@/connectors/google-workspace/api-errors' import { ConnectorSourceError, type ConnectorSourceFailureCategory, } from '@/connectors/source-error' -import { readBodyWithLimit } from '@/connectors/utils' -const GOOGLE_ERROR_BODY_MAX_BYTES = 64 * 1024 const GOOGLE_ERROR_REASON_MAX_COUNT = 16 const EXPORT_TOO_LARGE_REASONS = new Set(['exportSizeLimitExceeded']) @@ -52,50 +54,6 @@ export type GoogleDriveErrorKind = | 'unknown' | 'unsupported_export' -interface GoogleErrorEntry { - reason?: string -} - -interface ParsedGoogleErrorBody { - error?: { - errors?: GoogleErrorEntry[] - } -} - -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null -} - -function optionalString(value: unknown): string | undefined { - return typeof value === 'string' && value.trim() ? value.trim() : undefined -} - -function parseErrorBody(value: unknown): ParsedGoogleErrorBody | undefined { - if (!isRecord(value) || !isRecord(value.error)) return undefined - - const entries = Array.isArray(value.error.errors) - ? value.error.errors.flatMap((entry): GoogleErrorEntry[] => { - if (!isRecord(entry)) return [] - return [ - { - reason: optionalString(entry.reason), - }, - ] - }) - : undefined - - return { - error: { - errors: entries, - }, - } -} - -function normalizeReason(reason: string): string | undefined { - const normalized = reason.trim() - return /^[A-Za-z][A-Za-z0-9_.-]{0,99}$/.test(normalized) ? normalized : undefined -} - function classifyGoogleDriveError( status: number, reasons: readonly string[] @@ -147,14 +105,18 @@ export class GoogleDriveApiError extends ConnectorSourceError { readonly kind: GoogleDriveErrorKind readonly rateLimited: boolean - constructor(status: number, normalizedReasons: readonly string[]) { - const diagnosticReasons = normalizedReasons.slice(0, GOOGLE_ERROR_REASON_MAX_COUNT) + constructor(status: number, normalizedReasons: readonly string[], operation = 'drive.request') { + const diagnosticReasons = safeGoogleErrorReasons(normalizedReasons).slice( + 0, + GOOGLE_ERROR_REASON_MAX_COUNT + ) const reasonSuffix = diagnosticReasons.length > 0 ? ` (${diagnosticReasons.join(', ')})` : '' const kind = classifyGoogleDriveError(status, normalizedReasons) super( `Google Drive API request failed with HTTP ${status}${reasonSuffix}`, status, - diagnosticCategory(kind, status) + diagnosticCategory(kind, status), + { operation, reasons: diagnosticReasons } ) this.name = 'GoogleDriveApiError' this.reasons = diagnosticReasons @@ -169,24 +131,11 @@ export class GoogleDriveApiError extends ConnectorSourceError { * response body. Error payloads are byte-bounded, free-form provider messages * are omitted, and only validated machine-readable reason tokens survive. */ -export async function readGoogleDriveApiError(response: Response): Promise { - const body = await readBodyWithLimit(response, GOOGLE_ERROR_BODY_MAX_BYTES).catch(() => null) - let parsedBody: ParsedGoogleErrorBody | undefined - - if (body) { - try { - parsedBody = parseErrorBody(JSON.parse(body.toString('utf8'))) - } catch { - parsedBody = undefined - } - } - - const entries = parsedBody?.error?.errors ?? [] - const rawReasons = [...new Set(entries.flatMap((entry) => (entry.reason ? [entry.reason] : [])))] - const normalizedReasons = [ - ...new Set(rawReasons.flatMap((reason) => normalizeReason(reason) ?? [])), - ] - return new GoogleDriveApiError(response.status, normalizedReasons) +export async function readGoogleDriveApiError( + response: Response, + operation = 'drive.request' +): Promise { + return new GoogleDriveApiError(response.status, await readGoogleErrorReasons(response), operation) } /** @@ -198,14 +147,15 @@ export async function readGoogleDriveApiError(response: Response): Promise { return retryWithExponentialBackoff( async () => { const response = await fetch(url, options) if (response.ok) return response - const error = await readGoogleDriveApiError(response) + const error = await readGoogleDriveApiError(response, operation) attachRetryHeaders(error, response.headers) const waitMs = resolveRetryDelayMs(response.headers) if (waitMs !== undefined) error.retryAfterMs = waitMs diff --git a/apps/sim/connectors/google-drive/google-drive.test.ts b/apps/sim/connectors/google-drive/google-drive.test.ts index 06444b02cf4..f86a0b210cd 100644 --- a/apps/sim/connectors/google-drive/google-drive.test.ts +++ b/apps/sim/connectors/google-drive/google-drive.test.ts @@ -436,6 +436,7 @@ describe('Google Drive recursive folders and raw files', () => { .mockResolvedValueOnce(driveErrorResponse('insufficientFilePermissions', 'No download')) await expect(googleDriveConnector.getDocument('token', {}, FILE_ID)).rejects.toMatchObject({ kind: 'permission', + diagnostic: { operation: 'drive.files.get', reasons: ['insufficientFilePermissions'] }, }) }) }) @@ -512,7 +513,7 @@ describe('Google Drive API error parsing', () => { ) ) - expect(error.reasons).toEqual(reasons.slice(0, 16)) + expect(error.reasons).toEqual(['userRateLimitExceeded']) expect(error.kind).toBe('transient') expect(error.rateLimited).toBe(true) }) @@ -754,7 +755,8 @@ describe('Google Drive export failures', () => { name: 'GoogleDriveApiError', status: 403, kind: 'unknown', - reasons: ['newGoogleReason'], + reasons: [], + diagnostic: { operation: 'drive.files.export', reasons: [] }, }) }) diff --git a/apps/sim/connectors/google-drive/google-drive.ts b/apps/sim/connectors/google-drive/google-drive.ts index 88d9413f5a4..2d1539b7171 100644 --- a/apps/sim/connectors/google-drive/google-drive.ts +++ b/apps/sim/connectors/google-drive/google-drive.ts @@ -115,6 +115,7 @@ function googleDriveErrorLogFields(error: unknown): Record { error: error.message, status: error.status, reasons: error.reasons, + operation: error.diagnostic?.operation, } } return { error: toError(error).message } @@ -166,10 +167,12 @@ async function exportGoogleWorkspaceFile( let response: Response try { - response = await fetchGoogleDriveWithRetry(url, { - method: 'GET', - headers: driveRequestHeaders(accessToken, fileId, resourceKey), - }) + response = await fetchGoogleDriveWithRetry( + url, + { method: 'GET', headers: driveRequestHeaders(accessToken, fileId, resourceKey) }, + {}, + 'drive.files.export' + ) } catch (error) { if (error instanceof GoogleDriveApiError && error.kind === 'export_too_large') { throw new ConnectorFileTooLargeError(MAX_EXPORT_SIZE) @@ -194,10 +197,12 @@ async function downloadFile( // metadata fetch in getDocument already does. (`files.export` takes no such param.) const url = `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}?alt=media&supportsAllDrives=true` - const response = await fetchGoogleDriveWithRetry(url, { - method: 'GET', - headers: driveRequestHeaders(accessToken, fileId, resourceKey), - }) + const response = await fetchGoogleDriveWithRetry( + url, + { method: 'GET', headers: driveRequestHeaders(accessToken, fileId, resourceKey) }, + {}, + 'drive.files.get' + ) // Stream with a hard byte cap so a file with missing/under-reported listing // size metadata is never fully buffered into memory. Oversized files raise @@ -610,10 +615,12 @@ async function listFilePermissions( return `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}/permissions?${query.toString()}` }, fetch: (url) => - fetchGoogleDriveWithRetry(url, { - method: 'GET', - headers: driveRequestHeaders(accessToken, fileId, resourceKey), - }), + fetchGoogleDriveWithRetry( + url, + { method: 'GET', headers: driveRequestHeaders(accessToken, fileId, resourceKey) }, + {}, + 'drive.permissions.list' + ), parseError: (response) => response.json().catch(() => null), getItems: (body) => body.permissions, getNextPageToken: (body) => body.nextPageToken, @@ -755,7 +762,9 @@ async function readDriveFile( const fields = `${DRIVE_FILE_FIELDS}${permissions ? `,permissions(${DRIVE_PERMISSION_FIELDS})` : ''}` const response = await fetchGoogleDriveWithRetry( `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}?fields=${encodeURIComponent(fields)}&supportsAllDrives=true`, - { method: 'GET', headers: driveRequestHeaders(accessToken, fileId, resourceKey) } + { method: 'GET', headers: driveRequestHeaders(accessToken, fileId, resourceKey) }, + {}, + 'drive.files.get' ) return parseDriveFileMetadata(await readDriveJson(response, DRIVE_METADATA_MAX_BYTES), fileId) } @@ -942,7 +951,9 @@ async function listShortcutChanges( { method: 'GET', headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, - } + }, + {}, + 'drive.files.list' ) const page = parseDriveFileListResponse(await readDriveJson(response, DRIVE_PAGE_MAX_BYTES)) if (page.incompleteSearch) throw new Error('Google Drive shortcut search was incomplete') @@ -1114,14 +1125,16 @@ const listGoogleDriveDocuments: ConnectorConfig['listDocuments'] = async ( let response: Response try { - response = await fetchGoogleDriveWithRetry(url, { - method: 'GET', - signal: syncContext?.signal instanceof AbortSignal ? syncContext.signal : undefined, - headers: { - Authorization: `Bearer ${accessToken}`, - Accept: 'application/json', + response = await fetchGoogleDriveWithRetry( + url, + { + method: 'GET', + signal: syncContext?.signal instanceof AbortSignal ? syncContext.signal : undefined, + headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, }, - }) + {}, + 'drive.files.list' + ) } catch (error) { if ( (traversal || sharedDriveId) && @@ -1327,7 +1340,8 @@ export const googleDriveConnector: ConnectorConfig = { await fetchGoogleDriveWithRetry( 'https://www.googleapis.com/drive/v3/files?pageSize=1&fields=files(id)&corpora=user&supportsAllDrives=true&includeItemsFromAllDrives=true', { headers: { Authorization: `Bearer ${sampleToken}`, Accept: 'application/json' } }, - VALIDATE_RETRY_OPTIONS + VALIDATE_RETRY_OPTIONS, + 'drive.files.list' ) } return { valid: true } @@ -1347,7 +1361,8 @@ export const googleDriveConnector: ConnectorConfig = { Accept: 'application/json', }, }, - VALIDATE_RETRY_OPTIONS + VALIDATE_RETRY_OPTIONS, + 'drive.files.get' ) } catch (error) { if (error instanceof GoogleDriveApiError) { @@ -1383,7 +1398,8 @@ export const googleDriveConnector: ConnectorConfig = { Accept: 'application/json', }, }, - VALIDATE_RETRY_OPTIONS + VALIDATE_RETRY_OPTIONS, + 'drive.files.list' ) } catch (error) { if (error instanceof GoogleDriveApiError) { @@ -1434,10 +1450,15 @@ export const googleDriveConnector: ConnectorConfig = { getChangeCursor: async (accessToken: string): Promise => { const url = 'https://www.googleapis.com/drive/v3/changes/startPageToken?supportsAllDrives=true' - const response = await fetchGoogleDriveWithRetry(url, { - method: 'GET', - headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, - }) + const response = await fetchGoogleDriveWithRetry( + url, + { + method: 'GET', + headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, + }, + {}, + 'drive.changes.getStartPageToken' + ) const data: unknown = await response.json() if ( !isPlainRecord(data) || @@ -1481,10 +1502,15 @@ export const googleDriveConnector: ConnectorConfig = { let response: Response try { - response = await fetchGoogleDriveWithRetry(url, { - method: 'GET', - headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, - }) + response = await fetchGoogleDriveWithRetry( + url, + { + method: 'GET', + headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, + }, + {}, + 'drive.changes.list' + ) } catch (error) { logger.error('Failed to list Google Drive changes', googleDriveErrorLogFields(error)) throw error diff --git a/apps/sim/connectors/google-drive/workspace-drives.ts b/apps/sim/connectors/google-drive/workspace-drives.ts index 6722a552edf..4a45d30b745 100644 --- a/apps/sim/connectors/google-drive/workspace-drives.ts +++ b/apps/sim/connectors/google-drive/workspace-drives.ts @@ -41,7 +41,9 @@ export async function listGoogleWorkspaceDrives( { headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, signal, - } + }, + {}, + 'drive.drives.list' ) const body = await readBodyWithLimit(response, SHARED_DRIVES_PAGE_MAX_BYTES) if (!body) throw new Error('Google Drive shared-drive metadata exceeded its size limit') diff --git a/apps/sim/connectors/google-workspace/api-errors.test.ts b/apps/sim/connectors/google-workspace/api-errors.test.ts new file mode 100644 index 00000000000..f6e4ace1b8f --- /dev/null +++ b/apps/sim/connectors/google-workspace/api-errors.test.ts @@ -0,0 +1,147 @@ +/** @vitest-environment node */ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' +import { + fetchGoogleApiWithRetry, + readGoogleApiError, +} from '@/connectors/google-workspace/api-errors' + +const OPERATION = 'gmail.messages.attachments.get' +const RESPONSE_SECRET = 'private-customer-data' +function failure(status: number, reason: string, headers?: Record): Response { + return Response.json( + { error: { message: RESPONSE_SECRET, errors: [{ reason }] } }, + { status, headers } + ) +} + +afterEach(() => { + vi.unstubAllGlobals() + vi.useRealTimers() +}) + +describe('Google API diagnostics', () => { + it.each([ + [400, 'badRequest', 'request_rejected'], + [403, 'forbidden', 'authorization'], + [403, 'userRateLimitExceeded', 'rate_limit'], + [500, 'backendError', 'provider_unavailable'], + ] as const)('retains safe %s %s evidence through wrapping', async (status, reason, category) => { + const error = await readGoogleApiError(failure(status, reason), OPERATION) + const diagnostic = getConnectorFailureDiagnostic( + new Error('outer private detail', { cause: error }) + ) + expect(diagnostic).toMatchObject({ status, category, operation: OPERATION, reasons: [reason] }) + expect(JSON.stringify(diagnostic)).not.toContain(RESPONSE_SECRET) + expect(JSON.stringify(diagnostic)).not.toContain('outer private detail') + }) + + it('omits unknown reason tokens even when they look like machine codes', async () => { + const error = await readGoogleApiError(failure(403, RESPONSE_SECRET), OPERATION) + expect(error.diagnostic?.reasons).toEqual([]) + expect(JSON.stringify(error)).not.toContain(RESPONSE_SECRET) + }) + + it('reads structured ErrorInfo without its sensitive metadata', async () => { + const error = await readGoogleApiError( + Response.json( + { + error: { + details: [ + { + '@type': 'type.googleapis.com/google.rpc.ErrorInfo', + reason: 'SERVICE_DISABLED', + metadata: { credential: RESPONSE_SECRET }, + }, + ], + }, + }, + { status: 403 } + ), + 'calendar.events.list' + ) + expect(error.diagnostic?.reasons).toEqual(['SERVICE_DISABLED']) + expect(JSON.stringify(error)).not.toContain(RESPONSE_SECRET) + }) + + it.each(['not-json', 'x'.repeat(65 * 1024)])( + 'retains status for malformed or oversized bodies', + async (body) => { + const error = await readGoogleApiError(new Response(body, { status: 400 }), OPERATION) + expect(error.status).toBe(400) + expect(error.diagnostic?.reasons).toEqual([]) + } + ) +}) + +describe('Google API retries', () => { + it('preserves the caller admission hook and diagnoses its response', async () => { + const fetch = vi.fn().mockResolvedValueOnce(failure(403, 'forbidden')) + const fetcher = vi.fn( + (input: RequestInfo | URL, init: RequestInit, transport: typeof globalThis.fetch) => + transport(input, init) + ) + vi.stubGlobal('fetch', fetch) + await expect( + fetchGoogleApiWithRetry(OPERATION, 'https://gmail.googleapis.com/example', {}, { fetcher }) + ).rejects.toMatchObject({ + status: 403, + diagnostic: { operation: OPERATION, reasons: ['forbidden'] }, + }) + expect(fetcher).toHaveBeenCalledTimes(1) + expect(fetch).toHaveBeenCalledTimes(1) + expect(fetcher.mock.calls[0]?.[1].signal).toBeInstanceOf(AbortSignal) + }) + + it.each([ + [500, 'backendError'], + [403, 'rateLimitExceeded'], + [429, 'userRateLimitExceeded'], + ] as const)('retries %s %s through the bounded transport', async (status, reason) => { + vi.useFakeTimers() + const fetch = vi + .fn() + .mockResolvedValueOnce(failure(status, reason)) + .mockResolvedValueOnce(Response.json({ ok: true })) + vi.stubGlobal('fetch', fetch) + const request = fetchGoogleApiWithRetry( + OPERATION, + 'https://gmail.googleapis.com/example', + {}, + { maxRetries: 1, initialDelayMs: 1 } + ) + const checked = expect(request).resolves.toMatchObject({ status: 200 }) + await vi.runAllTimersAsync() + await checked + expect(fetch).toHaveBeenCalledTimes(2) + }) + + it.each([ + [400, 'failedPrecondition'], + [403, 'forbidden'], + [404, 'notFound'], + ] as const)('does not retry or suppress %s %s', async (status, reason) => { + const fetch = vi.fn().mockResolvedValueOnce(failure(status, reason)) + vi.stubGlobal('fetch', fetch) + await expect( + fetchGoogleApiWithRetry(OPERATION, 'https://gmail.googleapis.com/example', {}) + ).rejects.toMatchObject({ status, diagnostic: { reasons: [reason] } }) + expect(fetch).toHaveBeenCalledTimes(1) + }) + + it('retains Retry-After without exceeding the caller retry budget', async () => { + const fetch = vi + .fn() + .mockResolvedValueOnce(failure(429, 'rateLimitExceeded', { 'Retry-After': '300' })) + vi.stubGlobal('fetch', fetch) + await expect( + fetchGoogleApiWithRetry( + OPERATION, + 'https://gmail.googleapis.com/example', + {}, + { retryBudgetMs: 1000 } + ) + ).rejects.toMatchObject({ status: 429, retryAfterMs: 300000 }) + expect(fetch).toHaveBeenCalledTimes(1) + }) +}) diff --git a/apps/sim/connectors/google-workspace/api-errors.ts b/apps/sim/connectors/google-workspace/api-errors.ts new file mode 100644 index 00000000000..12653fd37c8 --- /dev/null +++ b/apps/sim/connectors/google-workspace/api-errors.ts @@ -0,0 +1,150 @@ +import { fetchWithRetry } from '@/lib/knowledge/documents/secure-fetch.server' +import { + attachRetryHeaders, + isRetryableError, + type RetryOptions, + resolveRetryDelayMs, +} from '@/lib/knowledge/documents/utils' +import { ConnectorSourceError } from '@/connectors/source-error' +import { readBodyWithLimit } from '@/connectors/utils' + +const ERROR_BODY_MAX_BYTES = 64 * 1024 +const MAX_REASONS = 16 +/** Only known provider codes may reach logs; messages and unknown strings may contain data. */ +const SAFE_REASONS = new Set([ + 'accessNotConfigured', + 'appNotAuthorizedToFile', + 'authError', + 'backendError', + 'badRequest', + 'cannotDownloadFile', + 'cannotExportFile', + 'dailyLimitExceeded', + 'domainPolicy', + 'download_restricted_for_revision', + 'exportSizeLimitExceeded', + 'failedPrecondition', + 'fileNotDownloadable', + 'fileNotExportable', + 'forbidden', + 'insufficientFilePermissions', + 'insufficientPermissions', + 'internalError', + 'invalid', + 'invalidArgument', + 'notFound', + 'quotaExceeded', + 'rateLimitExceeded', + 'required', + 'serviceDisabled', + 'sharingRateLimitExceeded', + 'teamDriveMembershipRequired', + 'userRateLimitExceeded', + 'ACCESS_TOKEN_SCOPE_INSUFFICIENT', + 'API_KEY_SERVICE_BLOCKED', + 'SERVICE_DISABLED', + 'RATE_LIMIT_EXCEEDED', + 'USER_PROJECT_DENIED', +]) +const RATE_LIMIT_REASONS = new Set([ + 'rateLimitExceeded', + 'userRateLimitExceeded', + 'sharingRateLimitExceeded', + 'RATE_LIMIT_EXCEEDED', +]) + +export function safeGoogleErrorReasons(reasons: readonly string[]): string[] { + return [...new Set(reasons.filter((reason) => SAFE_REASONS.has(reason)))] +} + +/** Reads the bounded Google envelope without retaining provider messages or request data. */ +export async function readGoogleErrorReasons(response: Response): Promise { + const body = await readBodyWithLimit(response, ERROR_BODY_MAX_BYTES).catch(() => null) + if (!body) return [] + try { + const payload: unknown = JSON.parse(body.toString('utf8')) + if (!payload || typeof payload !== 'object' || !('error' in payload)) return [] + const error = payload.error + if (!error || typeof error !== 'object') return [] + const entries = [ + ...('errors' in error && Array.isArray(error.errors) ? error.errors : []), + ...('details' in error && Array.isArray(error.details) ? error.details : []), + ] + return safeGoogleErrorReasons( + entries.flatMap((entry: unknown) => + entry && typeof entry === 'object' && 'reason' in entry && typeof entry.reason === 'string' + ? [entry.reason] + : [] + ) + ) + } catch { + return [] + } +} + +export class GoogleApiError extends ConnectorSourceError { + readonly rateLimited: boolean + retryAfterMs?: number + constructor(operation: string, status: number, reasons: readonly string[]) { + const safeReasons = safeGoogleErrorReasons(reasons) + const suffix = safeReasons.length ? ` (${safeReasons.join(', ')})` : '' + const category = + status === 429 || + safeReasons.some( + (reason) => + RATE_LIMIT_REASONS.has(reason) || + reason === 'dailyLimitExceeded' || + reason === 'quotaExceeded' + ) + ? 'rate_limit' + : status >= 500 + ? 'provider_unavailable' + : undefined + super(`${operation} failed (HTTP ${status})${suffix}.`, status, category, { + operation, + reasons: safeReasons.slice(0, MAX_REASONS), + }) + this.name = 'GoogleApiError' + this.rateLimited = + status === 429 || safeReasons.some((reason) => RATE_LIMIT_REASONS.has(reason)) + } +} + +export async function readGoogleApiError( + response: Response, + operation: string +): Promise { + return new GoogleApiError(operation, response.status, await readGoogleErrorReasons(response)) +} + +/** Preserves Google diagnostics when the shared transport retries a transient HTTP response. */ +function googleApiRetryOptions(operation: string, options: RetryOptions = {}): RetryOptions { + return { + ...options, + fetcher: async (input, init, transport) => { + const response = options.fetcher + ? await options.fetcher(input, init, transport) + : await transport(input, init) + if (!response.ok) { + const error = await readGoogleApiError(response, operation) + attachRetryHeaders(error, response.headers) + error.retryAfterMs = resolveRetryDelayMs(response.headers) + throw error + } + return response + }, + retryCondition: (error) => + error instanceof GoogleApiError && (error.status >= 500 || error.rateLimited) + ? true + : (options.retryCondition?.(error) ?? isRetryableError(error)), + } +} + +export function fetchGoogleApiWithRetry( + operation: string, + url: string, + options: RequestInit, + retryOptions: RetryOptions = {} +): Promise { + return fetchWithRetry(url, options, googleApiRetryOptions(operation, retryOptions)) +} diff --git a/apps/sim/connectors/google-workspace/users.ts b/apps/sim/connectors/google-workspace/users.ts index 762b43b1b2c..f5c2baf3204 100644 --- a/apps/sim/connectors/google-workspace/users.ts +++ b/apps/sim/connectors/google-workspace/users.ts @@ -110,7 +110,8 @@ export async function listGoogleWorkspaceUsers( headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, signal, }, - options.validate ? VALIDATE_RETRY_OPTIONS : undefined + options.validate ? VALIDATE_RETRY_OPTIONS : undefined, + 'directory.users.list' ) const data = await readDirectoryJson(response) if ( @@ -154,7 +155,8 @@ export async function getGoogleWorkspaceUser( headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' }, signal: options.signal, }, - options.validate ? VALIDATE_RETRY_OPTIONS : undefined + options.validate ? VALIDATE_RETRY_OPTIONS : undefined, + 'directory.users.get' ) return parseUser(await readDirectoryJson(response)) } catch (error) { diff --git a/apps/sim/connectors/source-error.ts b/apps/sim/connectors/source-error.ts index dd4745021d5..09e0c854e7f 100644 --- a/apps/sim/connectors/source-error.ts +++ b/apps/sim/connectors/source-error.ts @@ -11,9 +11,13 @@ export class ConnectorSourceError extends Error { constructor( message: string, readonly status: number, - readonly category?: ConnectorSourceFailureCategory + readonly category?: ConnectorSourceFailureCategory, + readonly diagnostic?: { operation: string; reasons: readonly string[] } ) { super(message) this.name = 'ConnectorSourceError' } } + +/** Keeps directory failures distinct from document-content failures through cause wrapping. */ +export class ConnectorDirectoryError extends Error {} diff --git a/apps/sim/lib/knowledge/connectors/connector-error.test.ts b/apps/sim/lib/knowledge/connectors/connector-error.test.ts index a1d52f97540..69a995c95e6 100644 --- a/apps/sim/lib/knowledge/connectors/connector-error.test.ts +++ b/apps/sim/lib/knowledge/connectors/connector-error.test.ts @@ -3,6 +3,7 @@ import { DrizzleQueryError } from 'drizzle-orm/errors' import { describe, expect, it } from 'vitest' import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' import { GoogleDriveApiError } from '@/connectors/google-drive/google-drive-errors' +import { ConnectorDirectoryError } from '@/connectors/source-error' describe('connector failure diagnostics', () => { it('retains the SQLSTATE while discarding SQL, bound values and driver detail', () => { @@ -85,6 +86,34 @@ describe('connector failure diagnostics', () => { expect(getConnectorFailureDiagnostic(error)?.message).not.toContain('access was denied') }) + it('reports a wrapped group-membership failure without suggesting file download permissions', () => { + const error = new Error('private outer message', { + cause: new ConnectorDirectoryError('private group detail', { + cause: new GoogleDriveApiError(403, ['forbidden'], 'directory.members.list'), + }), + }) + const diagnostic = getConnectorFailureDiagnostic(error) + expect(diagnostic).toMatchObject({ + phase: 'directory', + status: 403, + operation: 'directory.members.list', + reasons: ['forbidden'], + }) + expect(diagnostic?.message).toContain('Directory permission sync failed') + expect(diagnostic?.message).not.toContain('file access') + expect(JSON.stringify(diagnostic)).not.toContain('private') + }) + + it('keeps directory context when the failure has no HTTP status', () => { + expect( + getConnectorFailureDiagnostic(new ConnectorDirectoryError('private directory details')) + ).toMatchObject({ + category: 'directory', + phase: 'directory', + message: expect.stringContaining('Directory permission sync failed'), + }) + }) + it('does not infer status or permanence from a free-form message', () => { expect(getConnectorFailureDiagnostic(new Error('HTTP 403 permission denied'))).toBeNull() expect( diff --git a/apps/sim/lib/knowledge/connectors/connector-error.ts b/apps/sim/lib/knowledge/connectors/connector-error.ts index 7f8e4ac5179..6c7c09b3cc6 100644 --- a/apps/sim/lib/knowledge/connectors/connector-error.ts +++ b/apps/sim/lib/knowledge/connectors/connector-error.ts @@ -1,15 +1,19 @@ import { findCause, getPostgresErrorCode } from '@sim/utils/errors' import { DrizzleQueryError } from 'drizzle-orm/errors' import { + ConnectorDirectoryError, ConnectorSourceError, type ConnectorSourceFailureCategory, } from '@/connectors/source-error' export interface ConnectorFailureDiagnostic { - category: 'database' | ConnectorSourceFailureCategory | 'transport' + category: 'directory' | 'database' | ConnectorSourceFailureCategory | 'transport' message: string status?: number code?: string + operation?: string + reasons?: readonly string[] + phase?: 'directory' } const TRANSPORT_CODES = new Set([ @@ -30,7 +34,7 @@ const TRANSPORT_CODES = new Set([ * SQL, bound parameters, URLs and arbitrary exception messages never enter the * result. Unknown failures retain the caller's domain-specific fallback. */ -export function getConnectorFailureDiagnostic(error: unknown): ConnectorFailureDiagnostic | null { +function classifyFailure(error: unknown): ConnectorFailureDiagnostic | null { const code = getPostgresErrorCode(error) const databaseError = findCause( error, @@ -106,3 +110,35 @@ export function getConnectorFailureDiagnostic(error: unknown): ConnectorFailureD message: `Source content request was rejected (HTTP ${status}). Check the source's download restrictions and supported content.`, } } + +/** Preserves safe provider context and directory scope across wrapped failures. */ +export function getConnectorFailureDiagnostic(error: unknown): ConnectorFailureDiagnostic | null { + const diagnostic = classifyFailure(error) + const directoryError = findCause( + error, + (value): value is ConnectorDirectoryError => value instanceof ConnectorDirectoryError + ) + const sourceError = findCause( + error, + (value): value is ConnectorSourceError => value instanceof ConnectorSourceError + ) + const context = sourceError?.diagnostic + if (directoryError) { + const status = diagnostic?.status ? ` (HTTP ${diagnostic.status})` : '' + const reason = context?.reasons.length ? ` Google reason: ${context.reasons.join(', ')}.` : '' + return { + ...diagnostic, + ...context, + category: diagnostic?.category ?? 'directory', + phase: 'directory', + message: `Directory permission sync failed${status}.${context ? ` Operation: ${context.operation}.` : ''}${reason} Group membership could not be fully verified.`, + } + } + if (!diagnostic || !context) return diagnostic + const reason = context.reasons.length ? ` Google reason: ${context.reasons.join(', ')}.` : '' + return { + ...diagnostic, + ...context, + message: `Google request failed (HTTP ${diagnostic.status}). Operation: ${context.operation}.${reason}`, + } +} diff --git a/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts b/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts index 8dd68d764c0..90ae45c85c4 100644 --- a/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts +++ b/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts @@ -3,6 +3,7 @@ */ import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' +import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' import type { ConnectorDirectory } from '@/connectors/types' const { mockResolveTokenUserId, mockResolveToken, mockOpenDirectory, mockAvailability } = @@ -295,6 +296,11 @@ describe('refreshConnectorDirectory', () => { syncContext: {}, accessToken: 'token', }).catch((error: unknown) => error) + expect(getConnectorFailureDiagnostic(failure)).toMatchObject({ + phase: 'directory', + status: 429, + category: 'rate_limit', + }) expect(getRetryAfterMs(failure)).toBe(60_000) expect(isRateLimitError(failure)).toBe(true) }) diff --git a/apps/sim/lib/knowledge/connectors/external-group-sync.ts b/apps/sim/lib/knowledge/connectors/external-group-sync.ts index 221c6802483..5fbfc7c0bff 100644 --- a/apps/sim/lib/knowledge/connectors/external-group-sync.ts +++ b/apps/sim/lib/knowledge/connectors/external-group-sync.ts @@ -31,6 +31,7 @@ import { import { RUNNABLE_CONNECTOR_STATUSES } from '@/lib/knowledge/connectors/sync-lock' import { isRateLimitError } from '@/lib/knowledge/documents/utils' import { CONNECTOR_REGISTRY } from '@/connectors/registry.server' +import { ConnectorDirectoryError } from '@/connectors/source-error' import type { ConnectorConfig, ConnectorDirectory, @@ -382,7 +383,9 @@ export async function refreshMirroredDirectory(input: { connector: connectorConfig.id, error: getErrorMessage(error), }) - throw new Error(`${DIRECTORY_ERROR_PREFIX}${getErrorMessage(error)}`, { cause: error }) + throw new ConnectorDirectoryError(`${DIRECTORY_ERROR_PREFIX}${getErrorMessage(error)}`, { + cause: error, + }) } } diff --git a/apps/sim/lib/knowledge/documents/document-processing-source.test.ts b/apps/sim/lib/knowledge/documents/document-processing-source.test.ts index 24b77c321fa..a84e8fa96d5 100644 --- a/apps/sim/lib/knowledge/documents/document-processing-source.test.ts +++ b/apps/sim/lib/knowledge/documents/document-processing-source.test.ts @@ -11,6 +11,7 @@ import { schemaMock, setEnvFlags, } from '@sim/testing' +import { DrizzleQueryError } from 'drizzle-orm/errors' import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' const { @@ -20,6 +21,7 @@ const { mockGetBoundWorkspaceFileSecretProvenanceByMetadata, mockGetEmbeddingModelInfo, mockGetFileMetadataByKeys, + mockLogError, mockProcessDocument, mockTrigger, } = vi.hoisted(() => ({ @@ -29,10 +31,16 @@ const { mockGetBoundWorkspaceFileSecretProvenanceByMetadata: vi.fn(), mockGetEmbeddingModelInfo: vi.fn(), mockGetFileMetadataByKeys: vi.fn(), + mockLogError: vi.fn(), mockProcessDocument: vi.fn(), mockTrigger: vi.fn(), })) +vi.mock('@sim/logger', async () => { + const { createMockLogger, loggerMock } = await import('@sim/testing/mocks/logger.mock') + return { ...loggerMock, createLogger: () => ({ ...createMockLogger(), error: mockLogError }) } +}) + vi.mock('@trigger.dev/sdk', () => ({ tasks: { batchTrigger: mockBatchTrigger, trigger: mockTrigger }, })) @@ -693,6 +701,51 @@ describe('processDocumentAsync write guards', () => { expect(guardForStatusWrite('failed')).toBeDefined() }) + it('stores bounded database diagnostics while retaining the original error for retry classification', async () => { + dbChainMockFns.limit + .mockResolvedValueOnce([PERSISTED_CONTEXT]) + .mockResolvedValueOnce([PERSISTED_PROVENANCE_ROW]) + .mockResolvedValueOnce([{ id: 'document-1' }]) + mockGetFileMetadataByKeys.mockResolvedValue([SOURCE_BINDING]) + mockGetBoundWorkspaceFileSecretProvenanceByMetadata.mockResolvedValue( + new Map([[SOURCE_BINDING.id, { status: 'exact', entries: [] }]]) + ) + const databaseError = new DrizzleQueryError( + 'insert private SQL', + ['private bound content'], + Object.assign(new Error('private driver detail'), { code: '57014' }) + ) + mockProcessDocument.mockRejectedValueOnce(databaseError) + const onClaimed = vi.fn() + + await expect( + processDocumentAsync( + 'knowledge-base-1', + 'document-1', + { + filename: 'a.pdf', + fileUrl: 'https://example.com/a.pdf', + fileSize: 1, + mimeType: 'text/plain', + }, + {}, + BILLING_ATTRIBUTION, + 'request-1', + { chargedAtDispatch: true, onClaimed } + ) + ).rejects.toBe(databaseError) + + expect(dbChainMockFns.set).toHaveBeenCalledWith( + expect.objectContaining({ + processingStatus: 'failed', + processingError: 'Database request failed (SQLSTATE 57014).', + }) + ) + expect(onClaimed).toHaveBeenCalledTimes(1) + expect(guardForStatusWrite('processing')).toBeDefined() + expect(guardForStatusWrite('failed')).toBeDefined() + }) + it('accepts a legacy queuedAt-only payload only while the row has no token', async () => { dbChainMockFns.limit .mockResolvedValueOnce([PERSISTED_CONTEXT]) @@ -1441,6 +1494,34 @@ describe('in-process quota continuation dispatch', () => { ) }) + it('redacts database query details in the in-process worker without changing acceptance', async () => { + const databaseError = new DrizzleQueryError( + 'insert private-query', + ['private-parameter'], + Object.assign(new Error('private-driver-message'), { code: '57014' }) + ) + mockGenerateEmbeddings.mockRejectedValue(databaseError) + + await expect( + processDocumentsWithQueue( + [queuedDocument], + 'knowledge-base-1', + {}, + 'request-1', + BILLING_ATTRIBUTION + ) + ).resolves.toEqual({ requested: 1, accepted: 1, failed: 0, failedDocumentIds: [] }) + + expect(mockLogError).toHaveBeenCalledWith( + '[request-1] In-process document processing failed', + expect.objectContaining({ + error: 'Database request failed (SQLSTATE 57014).', + diagnostic: expect.objectContaining({ category: 'database', code: '57014' }), + }) + ) + expect(JSON.stringify(mockLogError.mock.calls)).not.toContain('private-') + }) + it('resumes an OCR-throttled regular KB from the durable outbox to a completed index', async () => { mockProcessDocument.mockRejectedValueOnce( new ProviderCapacityDeferredError('rate_limit', { retryAfterMs: 600_000 }) diff --git a/apps/sim/lib/knowledge/documents/service.ts b/apps/sim/lib/knowledge/documents/service.ts index 4b5d9cf2761..efc0e9e9fda 100644 --- a/apps/sim/lib/knowledge/documents/service.ts +++ b/apps/sim/lib/knowledge/documents/service.ts @@ -80,6 +80,7 @@ import { MAX_KNOWLEDGE_ACCESS_CANDIDATES, SYSTEM_ACCESS_SCOPE, } from '@/lib/knowledge/access/types' +import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' import { assertSyncLeaseHeldInTx, type SyncWriteLease } from '@/lib/knowledge/connectors/sync-lock' import { documentConnectorIsActive } from '@/lib/knowledge/documents/connector-lifecycle' import { @@ -1388,9 +1389,11 @@ async function dispatchInProcess( const message = processingClaimed ? 'In-process document processing failed' : 'In-process document dispatch failed before claiming the document' + const diagnostic = getConnectorFailureDiagnostic(error) logger.error(`[${requestId}] ${message}`, { documentId: p.documentId, - error: getErrorMessage(error), + error: diagnostic?.message ?? getErrorMessage(error), + ...(diagnostic ? { diagnostic } : {}), }) return processingClaimed } @@ -2060,15 +2063,19 @@ export async function processDocumentAsync( const providerContinuationExhausted = recordedError instanceof ProviderCapacityContinuationExhaustedError const quotaContinuationFailed = quotaContinuationAttempted && !deferredUntil + const failureDiagnostic = getConnectorFailureDiagnostic(recordedError) const errorMessage = byokCredentialRejected ? BYOK_EMBEDDING_CREDENTIAL_REJECTION_MESSAGE : embeddingQuotaExhausted ? quotaContinuationFailed ? getErrorMessage(recordedError, 'Embedding quota continuation dispatch failed') : EMBEDDING_QUOTA_EXHAUSTED_MESSAGE - : getErrorMessage(recordedError, 'Unknown error') + : failureDiagnostic?.category === 'database' + ? failureDiagnostic.message + : getErrorMessage(recordedError, 'Unknown error') const logContext = { errorType: toError(recordedError).name, + ...(failureDiagnostic ? { diagnostic: failureDiagnostic } : {}), knowledgeBaseId, mimeType: docData.mimeType, fileSize: docData.fileSize, From 90a05d9485d192096506cf5df31ba5a3a6222b9d Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 16 Sep 2026 15:50:11 -0700 Subject: [PATCH 27/43] feat(ci): promote Trigger.dev tasks in lockstep with the ECS traffic cutover (#5725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * v0.6.29: login improvements, posthog telemetry (#4026) * feat(posthog): Add tracking on mothership abort (#4023) Co-authored-by: Theodore Li * fix(login): fix captcha headers for manual login (#4025) * fix(signup): fix turnstile key loading * fix(login): fix captcha header passing * Catch user already exists, remove login form captcha * feat(ci): promote Trigger.dev tasks in lockstep with the ECS traffic cutover * fix(ci): harden Trigger.dev cutover gate — reject stale executions, verify all ECS targets * fix(ci): widen promote-trigger job timeout above the poll budget * fix(ci): hold AWS session for the full poll and skip wait when the app image is unchanged * feat(ci): extend lockstep Trigger.dev promotion to dev (preview branch) * fix(ci): don't block dev task promotion on a non-app build-dev leg failure * chore(ci): use one Trigger.dev PAT for all envs (drop DEV_TRIGGER_ACCESS_TOKEN) * fix(ci): reliable digest reads for no-op detection, robust version parse, pre-push dev epoch * fix(ci): give dev promote-trigger a 20-min margin over its poll budget * fix(ci): require promote-images + deploy-trigger success explicitly for promote-trigger * fix(ci): verify Trigger promotion against the deployed image and cutover * fix(ci): bind Trigger promotion to the latest app tag move * test(ci): reject exhausted deployment response fixtures * test(ci): require cutover checks for every ECS target --------- Co-authored-by: Waleed Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com> Co-authored-by: Vikhyath Mondreti --- .github/scripts/get-ecr-image-digest.sh | 28 ++ .github/scripts/promote-app-image.sh | 20 ++ .github/scripts/test-trigger-deploy.py | 264 ++++++++++++++++++ .github/scripts/wait-for-ecs-cutover.sh | 133 +++++++++ .github/workflows/ci.yml | 345 +++++++++++++++++++++++- .github/workflows/test-build.yml | 3 + 6 files changed, 782 insertions(+), 11 deletions(-) create mode 100644 .github/scripts/get-ecr-image-digest.sh create mode 100644 .github/scripts/promote-app-image.sh create mode 100644 .github/scripts/test-trigger-deploy.py create mode 100755 .github/scripts/wait-for-ecs-cutover.sh diff --git a/.github/scripts/get-ecr-image-digest.sh b/.github/scripts/get-ecr-image-digest.sh new file mode 100644 index 00000000000..f8de53f925e --- /dev/null +++ b/.github/scripts/get-ecr-image-digest.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Read one ECR tag. Only ImageNotFound is optional; AWS and response errors fail. +set -euo pipefail +REPOSITORY="${1:?repository required}" +TAG="${2:?tag required}" +ALLOW_MISSING="${3:-}" +if [ -n "$ALLOW_MISSING" ] && [ "$ALLOW_MISSING" != '--allow-missing' ]; then + echo 'ERROR: expected --allow-missing or no third argument' >&2 + exit 1 +fi +export AWS_PAGER='' +aws ecr batch-get-image --repository-name "$REPOSITORY" --image-ids imageTag="$TAG" --output json | + ALLOW_MISSING="$ALLOW_MISSING" python3 -c ' +import json, os, re, sys +response = json.load(sys.stdin) +images, failures = response["images"], response["failures"] +if failures: + if not images and len(failures) == 1 and failures[0]["failureCode"] == "ImageNotFound" and os.environ["ALLOW_MISSING"]: + print("") + sys.exit(0) + raise SystemExit("ERROR: ECR image lookup failed: " + ", ".join(f["failureCode"] for f in failures)) +if len(images) != 1: + raise SystemExit("ERROR: expected exactly one ECR image") +digest = images[0]["imageId"]["imageDigest"] +if not re.fullmatch(r"sha256:[0-9a-f]{64}", digest): + raise SystemExit("ERROR: invalid ECR image digest") +print(digest) +' diff --git a/.github/scripts/promote-app-image.sh b/.github/scripts/promote-app-image.sh new file mode 100644 index 00000000000..39cb88f23ff --- /dev/null +++ b/.github/scripts/promote-app-image.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Capture the cutover lower bound at the app tag move, after the image is built. +set -euo pipefail +REGISTRY="${1:?registry required}" +REPOSITORY="${2:?repository required}" +SOURCE_TAG="${3:?source tag required}" +DEPLOY_TAG="${4:?deploy tag required}" +: "${GITHUB_OUTPUT:?GitHub output file required}" +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +PREVIOUS=$(bash "$SCRIPT_DIR/get-ecr-image-digest.sh" "$REPOSITORY" "$DEPLOY_TAG" --allow-missing) +EPOCH=$(date +%s) +docker buildx imagetools create -t "$REGISTRY/$REPOSITORY:$DEPLOY_TAG" "$REGISTRY/$REPOSITORY:$SOURCE_TAG" +DIGEST=$(bash "$SCRIPT_DIR/get-ecr-image-digest.sh" "$REPOSITORY" "$DEPLOY_TAG") +CHANGED=true +if [ "$DIGEST" = "$PREVIOUS" ]; then CHANGED=false; fi +{ + echo "retag_epoch=$EPOCH" + echo "app_image_digest=$DIGEST" + echo "app_image_changed=$CHANGED" +} >> "$GITHUB_OUTPUT" diff --git a/.github/scripts/test-trigger-deploy.py b/.github/scripts/test-trigger-deploy.py new file mode 100644 index 00000000000..1ea31f4961a --- /dev/null +++ b/.github/scripts/test-trigger-deploy.py @@ -0,0 +1,264 @@ +"""Exercise the deployment gates with scripted AWS responses; no live mutations.""" +import json +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + +SCRIPTS = Path(__file__).resolve().parent +DIGEST = 'sha256:' + 'a' * 64 +OTHER_DIGEST = 'sha256:' + 'b' * 64 + + +def execution(start=1000, digest=DIGEST, identifier='execution-current'): + return { + 'startTime': start, + 'pipelineExecutionId': identifier, + 'sourceRevisions': [{'actionName': 'ECR_Source', 'revisionId': digest}], + } + + +class DeploymentGateTests(unittest.TestCase): + def run_script(self, script, args, responses): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + fixture = root / 'responses.json' + fixture.write_text(json.dumps(responses)) + (root / 'aws').write_text('''#!/usr/bin/env python3 +import json, os, pathlib, sys +root = pathlib.Path(os.environ['FIXTURE_DIR']) +args = sys.argv[1:] +if args[0] == '--cli-connect-timeout': + args = args[4:] +service, operation = args[:2] +key = operation +if operation == 'get-deployment-target': + key += ':' + args[args.index('--target-id') + 1] +with (root / 'calls').open('a') as stream: + stream.write(' '.join(args) + '\\n') +responses = json.loads((root / 'responses.json').read_text()) +if key not in responses: + raise SystemExit('Unexpected AWS call: ' + key) +response = responses[key] +# Objects model steady state; lists are finite, ordered expectations. +if isinstance(response, list): + if not response: + raise SystemExit('Unexpected extra AWS call: ' + key) + next_response = response.pop(0) + (root / 'responses.json').write_text(json.dumps(responses)) + response = next_response +if response.get('error'): + sys.stderr.write(response['error']) + sys.exit(254) +if response.get('advance_clock'): + clock = root / 'clock' + value = int(clock.read_text()) if clock.exists() else 1000 + clock.write_text(str(value + response['advance_clock'])) +print(response.get('text', json.dumps(response.get('json')))) +''') + (root / 'docker').write_text('''#!/usr/bin/env python3 +import os, pathlib, sys +root = pathlib.Path(os.environ['FIXTURE_DIR']) +with (root / 'calls').open('a') as stream: + stream.write('docker ' + ' '.join(sys.argv[1:]) + '\\n') +''') + (root / 'date').write_text('''#!/usr/bin/env python3 +import os, pathlib +path = pathlib.Path(os.environ['FIXTURE_DIR']) / 'clock' +value = int(path.read_text()) if path.exists() else 1000 +path.write_text(str(value + 1)) +print(value) +''') + (root / 'sleep').write_text('#!/bin/sh\nexit 0\n') + for name in ('aws', 'date', 'sleep', 'docker'): + (root / name).chmod(0o755) + result = subprocess.run( + ['bash', str(SCRIPTS / script), *args], + env={**os.environ, 'PATH': f'{root}:{os.environ["PATH"]}', + 'FIXTURE_DIR': str(root), 'POLL_INTERVAL': '1', 'OVERALL_TIMEOUT': '12', + 'GITHUB_OUTPUT': str(root / 'outputs')}, + capture_output=True, text=True, timeout=10, + ) + calls = (root / 'calls').read_text() if (root / 'calls').exists() else '' + result.github_output = (root / 'outputs').read_text() if (root / 'outputs').exists() else '' + return result, calls + + def poll(self, updates=None, since='1000'): + responses = { + 'list-pipeline-executions': {'json': [execution()]}, + 'get-pipeline-execution': {'text': 'InProgress'}, + 'list-action-executions': {'text': 'd-current'}, + 'get-deployment': {'text': 'InProgress'}, + 'list-deployment-targets': {'text': 'target-one\ttarget-two'}, + 'get-deployment-target:target-one': {'text': 'Succeeded'}, + 'get-deployment-target:target-two': {'text': 'Succeeded'}, + } + responses.update(updates or {}) + return self.run_script('wait-for-ecs-cutover.sh', ['app-pipeline', DIGEST, since], responses) + + def test_waits_for_every_target(self): + targets = ('target-one', 'target-two') + for pending_target in targets: + with self.subTest(pending_target=pending_target): + result, calls = self.poll({f'get-deployment-target:{pending_target}': [ + {'text': 'InProgress'}, {'text': 'Succeeded'}]}) + self.assertEqual(result.returncode, 0, result.stderr) + for target in targets: + self.assertEqual(calls.count(f'--target-id {target}'), 2) + + def test_rejects_stale_execution_inside_former_clock_skew_window(self): + result, calls = self.poll({'list-pipeline-executions': {'json': [execution(start=999)]}}) + self.assertNotEqual(result.returncode, 0) + self.assertIn('timed out', result.stdout) + self.assertNotIn('get-pipeline-execution ', calls) + + def test_chooses_newest_matching_execution(self): + result, calls = self.poll({'list-pipeline-executions': {'json': [ + execution(1000, identifier='execution-old'), execution(1001)]}}) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn('--pipeline-execution-id execution-current', calls) + + def test_changed_image_rejects_newer_different_execution(self): + result, calls = self.poll({'list-pipeline-executions': {'json': [ + execution(), execution(1001, digest=OTHER_DIGEST, identifier='execution-newer')]}}) + self.assertNotEqual(result.returncode, 0) + self.assertIn('deployment was superseded', result.stderr) + self.assertNotIn('get-pipeline-execution ', calls) + + def test_rechecks_latest_digest_after_cutover(self): + result, calls = self.poll({'list-pipeline-executions': [ + {'json': [execution()]}, + {'json': [execution(), execution(1001, digest=OTHER_DIGEST, identifier='execution-newer')]}, + ]}) + self.assertNotEqual(result.returncode, 0) + self.assertIn('deployment was superseded', result.stderr) + self.assertIn('get-deployment-target ', calls) + self.assertNotIn('Traffic cutover complete', result.stdout) + + def test_rechecks_execution_identity_for_same_digest_after_cutover(self): + result, _ = self.poll({'list-pipeline-executions': [ + {'json': [execution()]}, + {'json': [execution(), execution(1001, identifier='execution-newer')]}, + ]}) + self.assertNotEqual(result.returncode, 0) + self.assertIn('newer pipeline execution appeared', result.stdout) + self.assertNotIn('Traffic cutover complete', result.stdout) + + def test_iso_timestamps(self): + result, _ = self.poll({'list-pipeline-executions': {'json': [ + execution('1970-01-01T00:16:40+00:00')]}}) + self.assertEqual(result.returncode, 0, result.stderr) + + def test_scripted_responses_reject_unexpected_extra_calls(self): + result, _ = self.poll({'list-pipeline-executions': [{'json': [execution()]}]}) + self.assertNotEqual(result.returncode, 0) + self.assertIn('Unexpected extra AWS call: list-pipeline-executions', result.stderr) + self.assertNotIn('Traffic cutover complete', result.stdout) + + def test_access_denial_fails_immediately(self): + result, calls = self.poll({'list-pipeline-executions': {'error': 'AccessDeniedException'}}) + self.assertNotEqual(result.returncode, 0) + self.assertIn('AccessDeniedException', result.stderr) + self.assertEqual(len(calls.splitlines()), 1) + + def test_credentials_expiring_during_target_poll_fail(self): + result, _ = self.poll({'get-deployment-target:target-two': {'error': 'ExpiredToken'}}) + self.assertNotEqual(result.returncode, 0) + self.assertIn('ExpiredToken', result.stderr) + + def test_failed_and_superseded_pipeline_never_reach_deployment(self): + for status in ('Failed', 'Stopped', 'Superseded'): + with self.subTest(status=status): + result, calls = self.poll({'get-pipeline-execution': {'text': status}}) + self.assertNotEqual(result.returncode, 0) + self.assertNotIn('get-deployment ', calls) + + def test_waits_for_queued_deploy_action(self): + result, calls = self.poll({'list-action-executions': [{'text': 'None'}, {'text': 'd-current'}]}) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(calls.count('list-action-executions '), 2) + + def test_failed_deployment_never_accepts_old_cutover(self): + result, calls = self.poll({'get-deployment': {'text': 'Failed'}}) + self.assertNotEqual(result.returncode, 0) + self.assertNotIn('get-deployment-target ', calls) + + def test_empty_targets_cannot_satisfy_gate(self): + result, _ = self.poll({'list-deployment-targets': {'text': ''}}) + self.assertNotEqual(result.returncode, 0) + self.assertIn('timed out', result.stdout) + + def test_failed_target_fails_immediately(self): + result, _ = self.poll({'get-deployment-target:target-two': {'text': 'Failed'}}) + self.assertNotEqual(result.returncode, 0) + self.assertIn('cutover status Failed', result.stdout) + + def test_unchanged_image_verifies_existing_cutover(self): + result, calls = self.poll({'list-pipeline-executions': {'json': [execution(start=900)]}}, since='0') + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn('get-deployment-target ', calls) + + def test_unchanged_image_rejects_latest_different_deploy(self): + result, calls = self.poll({'list-pipeline-executions': {'json': [ + execution(start=900), execution(start=999, digest=OTHER_DIGEST)]}}, since='0') + self.assertNotEqual(result.returncode, 0) + self.assertIn('cutover is unverified', result.stderr) + self.assertNotIn('get-deployment ', calls) + + def test_unchanged_image_rejects_failed_previous_deploy(self): + result, _ = self.poll({'get-deployment': {'text': 'Failed'}}, since='0') + self.assertNotEqual(result.returncode, 0) + + def test_invalid_metadata_fails_before_aws(self): + result, calls = self.poll(since='corrupted') + self.assertNotEqual(result.returncode, 0) + self.assertEqual(calls, '') + + def test_ecr_digest_and_missing_tag(self): + result, _ = self.run_script('get-ecr-image-digest.sh', ['app', 'deploy'], { + 'batch-get-image': {'json': {'images': [{'imageId': {'imageDigest': DIGEST}}], 'failures': []}}}) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), DIGEST) + missing = {'batch-get-image': {'json': {'images': [], 'failures': [{'failureCode': 'ImageNotFound'}]}}} + result, _ = self.run_script('get-ecr-image-digest.sh', ['app', 'deploy', '--allow-missing'], missing) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), '') + result, _ = self.run_script('get-ecr-image-digest.sh', ['app', 'deploy'], missing) + self.assertNotEqual(result.returncode, 0) + + def test_ecr_response_failures_are_not_missing_images(self): + for response in ({'error': 'AccessDeniedException'}, {'json': {'images': [], 'failures': [{'failureCode': 'KmsError'}]}}, {'json': {'images': [], 'failures': []}}): + with self.subTest(response=response): + result, _ = self.run_script('get-ecr-image-digest.sh', ['app', 'deploy', '--allow-missing'], {'batch-get-image': response}) + self.assertNotEqual(result.returncode, 0) + + def test_tag_move_uses_push_boundary_and_final_manifest_digest(self): + result, calls = self.run_script('promote-app-image.sh', ['registry', 'app', 'commit-dev', 'dev'], { + 'batch-get-image': [ + {'advance_clock': 30, 'json': {'images': [{'imageId': {'imageDigest': DIGEST}}], 'failures': []}}, + {'json': {'images': [{'imageId': {'imageDigest': OTHER_DIGEST}}], 'failures': []}}, + ]}) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn('retag_epoch=1030', result.github_output) + self.assertIn(f'app_image_digest={OTHER_DIGEST}', result.github_output) + self.assertIn('app_image_changed=true', result.github_output) + self.assertEqual([line.split()[0] for line in calls.splitlines()], ['ecr', 'docker', 'ecr']) + self.assertIn('registry/app:commit-dev', calls) + + def test_tag_move_aborts_before_docker_when_ecr_read_fails(self): + result, calls = self.run_script('promote-app-image.sh', ['registry', 'app', 'commit', 'deploy'], { + 'batch-get-image': {'error': 'AccessDeniedException'}}) + self.assertNotEqual(result.returncode, 0) + self.assertNotIn('docker', calls) + self.assertEqual(result.github_output, '') + + def test_same_digest_tag_move_reports_unchanged(self): + result, _ = self.run_script('promote-app-image.sh', ['registry', 'app', 'commit', 'deploy'], { + 'batch-get-image': {'json': {'images': [{'imageId': {'imageDigest': DIGEST}}], 'failures': []}}}) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn('app_image_changed=false', result.github_output) + + +if __name__ == '__main__': + unittest.main() diff --git a/.github/scripts/wait-for-ecs-cutover.sh b/.github/scripts/wait-for-ecs-cutover.sh new file mode 100755 index 00000000000..b18758378fa --- /dev/null +++ b/.github/scripts/wait-for-ecs-cutover.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# Resolve a pushed app digest to CodePipeline -> CodeDeploy -> every ECS target's +# AllowTraffic event. An unchanged tag uses since-epoch=0 to verify the latest +# pipeline execution instead of assuming the tagged image is already serving. +# Usage: wait-for-ecs-cutover.sh +set -euo pipefail + +PIPELINE="${1:?pipeline name required}" +DIGEST="${2:?image digest required}" +SINCE_EPOCH="${3:?since-epoch required}" +POLL_INTERVAL="${POLL_INTERVAL:-15}" +OVERALL_TIMEOUT="${OVERALL_TIMEOUT:-4200}" +if ! [[ "$PIPELINE" =~ ^[A-Za-z0-9.@_-]+$ && "$DIGEST" =~ ^sha256:[0-9a-f]{64}$ && "$SINCE_EPOCH" =~ ^[0-9]+$ && "$POLL_INTERVAL" =~ ^[1-9][0-9]*$ && "$OVERALL_TIMEOUT" =~ ^[1-9][0-9]*$ ]]; then + echo 'ERROR: invalid pipeline, digest, epoch, or polling budget' >&2 + exit 1 +fi +export AWS_PAGER='' +export AWS_RETRY_MODE=standard +export AWS_MAX_ATTEMPTS=3 + +deadline=$(( $(date +%s) + OVERALL_TIMEOUT )) +log() { echo "[wait-for-ecs-cutover] $*"; } +check_deadline() { + if [ "$(date +%s)" -ge "$deadline" ]; then + log "ERROR: timed out after ${OVERALL_TIMEOUT}s waiting for $1" + exit 1 + fi +} +aws_read() { + aws --cli-connect-timeout 10 --cli-read-timeout 30 "$@" +} + +find_execution() { + local executions + executions=$(aws_read codepipeline list-pipeline-executions \ + --pipeline-name "$PIPELINE" --max-items 30 \ + --query 'pipelineExecutionSummaries' --output json) + printf '%s\n' "$executions" | SINCE="$SINCE_EPOCH" DIGEST="$DIGEST" python3 -c ' +import datetime, json, os, sys +since = int(os.environ["SINCE"]) +def epoch(execution): + value = execution["startTime"] + if isinstance(value, (int, float)): + return value + return datetime.datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp() +def matches(execution): + return any(r["actionName"] == "ECR_Source" and r.get("revisionId") == os.environ["DIGEST"] for r in execution.get("sourceRevisions", [])) +executions = sorted(json.load(sys.stdin), key=epoch, reverse=True) +if since == 0: + if not executions or not matches(executions[0]): + raise SystemExit("ERROR: unchanged app tag does not match the latest pipeline execution; cutover is unverified") + selected = executions[0] +else: + selected = executions[0] if executions and epoch(executions[0]) >= since else None + if selected and not matches(selected): + raise SystemExit("ERROR: latest pipeline execution does not match this app digest; deployment was superseded or its source is unverified") +print(selected["pipelineExecutionId"] if selected else "") +' +} + +EXECUTION_ID='' +while [ -z "$EXECUTION_ID" ]; do + check_deadline 'the matching pipeline execution' + EXECUTION_ID=$(find_execution) + if [ -z "$EXECUTION_ID" ]; then + log 'No matching execution since this push; waiting' + sleep "$POLL_INTERVAL" + fi +done +log "Matched pipeline execution: $EXECUTION_ID" + +DEPLOYMENT_ID='' +while [ -z "$DEPLOYMENT_ID" ] || [ "$DEPLOYMENT_ID" = 'None' ]; do + check_deadline 'the CodeDeploy deployment (the Deploy stage may be queued)' + status=$(aws_read codepipeline get-pipeline-execution \ + --pipeline-name "$PIPELINE" --pipeline-execution-id "$EXECUTION_ID" \ + --query 'pipelineExecution.status' --output text) + case "$status" in + Failed|Stopped|Stopping|Superseded|Cancelled) + log "ERROR: pipeline execution ended in $status; not promoting"; exit 1 ;; + InProgress|Succeeded) ;; + *) log "ERROR: unexpected pipeline status: $status"; exit 1 ;; + esac + DEPLOYMENT_ID=$(aws_read codepipeline list-action-executions \ + --pipeline-name "$PIPELINE" --filter pipelineExecutionId="$EXECUTION_ID" \ + --query "actionExecutionDetails[?stageName=='Deploy'].output.executionResult.externalExecutionId | [0]" \ + --output text) + if [ -z "$DEPLOYMENT_ID" ] || [ "$DEPLOYMENT_ID" = 'None' ]; then + if [ "$status" = 'Succeeded' ]; then + log 'ERROR: successful pipeline has no CodeDeploy deployment'; exit 1 + fi + sleep "$POLL_INTERVAL" + fi +done +log "CodeDeploy deployment: $DEPLOYMENT_ID" + +while true; do + check_deadline 'AllowTraffic on every ECS target' + status=$(aws_read deploy get-deployment --deployment-id "$DEPLOYMENT_ID" \ + --query 'deploymentInfo.status' --output text) + case "$status" in + Failed|Stopped) log "ERROR: deployment ended in $status; not promoting"; exit 1 ;; + Created|Queued|InProgress|Baking|Ready|Succeeded) ;; + *) log "ERROR: unexpected deployment status: $status"; exit 1 ;; + esac + target_ids=$(aws_read deploy list-deployment-targets --deployment-id "$DEPLOYMENT_ID" \ + --query 'targetIds' --output text) + if [ -n "$target_ids" ] && [ "$target_ids" != 'None' ]; then + all_ok=1 + for target in $target_ids; do + cutover=$(aws_read deploy get-deployment-target --deployment-id "$DEPLOYMENT_ID" --target-id "$target" \ + --query "deploymentTarget.ecsTarget.lifecycleEvents[?lifecycleEventName=='AllowTraffic'].status | [0]" \ + --output text) + case "$cutover" in + Succeeded) ;; + Failed|Skipped|Unknown) log "ERROR: target $target cutover status $cutover"; exit 1 ;; + Pending|InProgress|None|'') all_ok=0 ;; + *) log "ERROR: unexpected cutover status: $cutover"; exit 1 ;; + esac + done + if [ "$all_ok" = 1 ]; then + LATEST_EXECUTION_ID=$(find_execution) + if [ "$LATEST_EXECUTION_ID" != "$EXECUTION_ID" ]; then + log 'ERROR: a newer pipeline execution appeared during cutover; not promoting' + exit 1 + fi + log 'Traffic cutover complete on every ECS target' + exit 0 + fi + fi + log 'Traffic cutover is not complete; waiting' + sleep "$POLL_INTERVAL" +done diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75d45ac5e46..b8d9c64982b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -221,18 +221,210 @@ jobs: provider: ${{ vars.CI_PROVIDER }} file: ${{ matrix.dockerfile }} platforms: linux/amd64 - tags: ${{ steps.login-ecr.outputs.registry }}/${{ steps.ecr-repo.outputs.name }}:dev + tags: ${{ steps.login-ecr.outputs.registry }}/${{ steps.ecr-repo.outputs.name }}:${{ matrix.ecr_repo_secret == 'ECR_APP' && format('{0}-dev', github.sha) || 'dev' }} max-cache-size-mb: ${{ matrix.cache_mb }} - # Dev: deploy Trigger.dev background tasks to the preview "dev-sim" branch. - # Gated after migrate-dev for the same reason as build-dev — the new task - # code runs against the dev DB, so the schema must be pushed first. + - name: Promote dev app image + id: appdeploy + if: matrix.ecr_repo_secret == 'ECR_APP' + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + REPOSITORY: ${{ steps.ecr-repo.outputs.name }} + run: bash .github/scripts/promote-app-image.sh "$REGISTRY" "$REPOSITORY" "${GITHUB_SHA}-dev" dev + + - name: Publish dev cutover metadata + if: matrix.ecr_repo_secret == 'ECR_APP' + env: + DIGEST: ${{ steps.appdeploy.outputs.app_image_digest }} + EPOCH: ${{ steps.appdeploy.outputs.retag_epoch }} + CHANGED: ${{ steps.appdeploy.outputs.app_image_changed }} + run: | + mkdir -p dev-meta + echo "$DIGEST" > dev-meta/digest.txt + echo "$EPOCH" > dev-meta/retag_epoch.txt + echo "$CHANGED" > dev-meta/app_image_changed.txt + + - name: Upload dev cutover metadata + if: matrix.ecr_repo_secret == 'ECR_APP' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: dev-cutover-meta + path: dev-meta/ + retention-days: 1 + + # Dev: build & upload the Trigger.dev task version WITHOUT promoting it + # (--skip-promotion) to the preview "dev-sim" branch. promote-trigger-dev flips + # it at the dev ECS traffic cutover. Gated after migrate-dev so the schema is + # pushed before the new task version can run against the dev DB. deploy-trigger-dev: name: Deploy Trigger.dev (Dev) needs: [migrate-dev] if: github.event_name == 'push' && github.ref == 'refs/heads/dev' runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 15 + outputs: + version: ${{ steps.deploy.outputs.deploymentVersion }} + steps: + - name: Checkout code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.4.1 + + - name: Cache Bun dependencies + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + with: + path: | + ~/.bun/install/cache + node_modules + **/node_modules + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install dependencies + run: bun install --frozen-lockfile --ignore-scripts + + - name: Deploy to Trigger.dev (skip promotion) + id: deploy + working-directory: ./apps/sim + env: + TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} + TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }} + run: | + set -eo pipefail + if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then + echo "ERROR: TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2 + exit 1 + fi + bunx trigger.dev@4.5.12 deploy --env preview --branch dev-sim --skip-promotion + + - name: Validate deployment version output + env: + VERSION: ${{ steps.deploy.outputs.deploymentVersion }} + run: | + if ! [[ "$VERSION" =~ ^[0-9]{8}\.[0-9]+$ ]]; then + echo "ERROR: Trigger.dev did not report a valid deploymentVersion output" >&2 + exit 1 + fi + + # Dev: promote the skip-promoted preview version at the dev ECS traffic cutover. + # The dev app build moves :dev only after building its commit-tagged image, + # then passes the tag digest and retag timestamp through an artifact. + # trigger.dev supports promoting a specific preview branch: promote --env preview + # --branch dev-sim. + promote-trigger-dev: + name: Promote Trigger.dev (Dev) + needs: [build-dev, deploy-trigger-dev] + # Run as long as the task upload succeeded, even if a NON-app build-dev leg + # (realtime/pii/migrations) failed: the app leg pushes :dev independently and + # may have already triggered the ECS deploy, so an unrelated image failure must + # not strand the app on the old task version. The app-metadata artifact (only + # the app leg uploads it) is the real signal that an app deploy happened. + if: >- + !cancelled() && + github.event_name == 'push' && github.ref == 'refs/heads/dev' && + needs.deploy-trigger-dev.result == 'success' + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} + # Dev bake is 5 min and dev deploys don't queue behind a bake (serialized by the + # ci- group), so a 20-min poll is ample; the 40-min job leaves ~20 min for + # setup + promote above it (mirrors the prod 90-vs-70 margin), and the 40-min + # session outlasts the poll. + timeout-minutes: 40 + permissions: + contents: read + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.4.1 + + - name: Cache Bun dependencies + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + with: + path: | + ~/.bun/install/cache + node_modules + **/node_modules + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install dependencies + run: bun install --frozen-lockfile --ignore-scripts + + - name: Download dev cutover metadata + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + with: + name: dev-cutover-meta + path: dev-meta + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6 + with: + role-to-assume: ${{ secrets.DEV_AWS_ROLE_TO_ASSUME }} + aws-region: ${{ secrets.DEV_AWS_REGION }} + role-duration-seconds: 2400 + + - name: Wait for ECS traffic cutover + env: + OVERALL_TIMEOUT: "1200" + run: | + set -eo pipefail + CHANGED=$(cat dev-meta/app_image_changed.txt) + DIGEST=$(cat dev-meta/digest.txt) + EPOCH=$(cat dev-meta/retag_epoch.txt) + case "$CHANGED" in + true) ;; + false) EPOCH=0 ;; + *) echo "ERROR: invalid app image change metadata" >&2; exit 1 ;; + esac + bash .github/scripts/wait-for-ecs-cutover.sh sim-dev-us-east-1-app-deployment "$DIGEST" "$EPOCH" + + - name: Promote Trigger.dev version + working-directory: ./apps/sim + env: + TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} + TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }} + VERSION: ${{ needs.deploy-trigger-dev.outputs.version }} + run: | + set -eo pipefail + if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then + echo "ERROR: TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2 + exit 1 + fi + if [ -z "$VERSION" ]; then + echo "ERROR: no deployed version passed from deploy-trigger-dev" >&2 + exit 1 + fi + echo "Promoting Trigger.dev version $VERSION (preview / dev-sim)" + bunx trigger.dev@4.5.12 promote "$VERSION" --env preview --branch dev-sim + + # Main/staging: build & upload the Trigger.dev task version WITHOUT promoting it + # (--skip-promotion). New runs keep executing the OLD promoted version until + # promote-trigger flips it at the ECS traffic cutover — so the app cutting over + # never changes which task version runs until promote-trigger (which depends on + # this job) promotes the version uploaded here. Runs in parallel with the build; + # intentionally NOT gating the app deploy on it, to avoid coupling every app / + # realtime / pii / migration deploy to trigger.dev availability. + deploy-trigger: + name: Deploy Trigger.dev + needs: [migrate] + if: >- + !cancelled() && + needs.migrate.result == 'success' && + github.event_name == 'push' && + (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 15 + outputs: + version: ${{ steps.deploy.outputs.deploymentVersion }} steps: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -256,17 +448,29 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile --ignore-scripts - - name: Deploy to Trigger.dev + - name: Deploy to Trigger.dev (skip promotion) + id: deploy working-directory: ./apps/sim env: - TRIGGER_ACCESS_TOKEN: ${{ secrets.DEV_TRIGGER_ACCESS_TOKEN }} + TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }} + TRIGGER_ENV: ${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }} run: | + set -eo pipefail if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then - echo "ERROR: DEV_TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2 + echo "ERROR: TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2 + exit 1 + fi + bunx trigger.dev@4.5.12 deploy --env "$TRIGGER_ENV" --skip-promotion + + - name: Validate deployment version output + env: + VERSION: ${{ steps.deploy.outputs.deploymentVersion }} + run: | + if ! [[ "$VERSION" =~ ^[0-9]{8}\.[0-9]+$ ]]; then + echo "ERROR: Trigger.dev did not report a valid deploymentVersion output" >&2 exit 1 fi - bunx trigger.dev@4.5.12 deploy --env preview --branch dev-sim # Main/staging: build AMD64 images and push sha-tagged images to ECR + GHCR. # Runs in parallel with tests — only immutable sha tags are pushed here, and @@ -415,7 +619,22 @@ jobs: permissions: contents: read id-token: write + outputs: + # Whether the deploy tag was actually moved (false on a stale-run guard + # skip). promote-trigger keys off this so tasks are never promoted when + # the app itself wasn't. + promoted: ${{ steps.guard.outputs.fresh }} + # Epoch when the deploy tag was retagged (this push's ECS pipeline trigger). + # promote-trigger passes it to the poll script so a stale pipeline execution + # reusing the same image digest can't satisfy the cutover gate. + retag_epoch: ${{ steps.promote.outputs.retag_epoch }} + # Unchanged tags verify the latest execution's cutover without an epoch bound. + app_image_changed: ${{ steps.promote.outputs.app_image_changed }} + app_image_digest: ${{ steps.promote.outputs.app_image_digest }} steps: + - name: Checkout code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6 with: @@ -444,6 +663,7 @@ jobs: fi - name: Promote images to deploy tags + id: promote if: steps.guard.outputs.fresh == 'true' env: ECR_REPOS: >- @@ -460,6 +680,8 @@ jobs: ECR_TAG="staging" fi + APP_REPO="${{ secrets.ECR_APP }}" + # Verify every sha image exists before moving any deploy tag, so a # missing/expired image aborts the whole promotion up front. for repo in $ECR_REPOS; do @@ -469,11 +691,112 @@ jobs: for repo in $ECR_REPOS; do echo "🚀 Promoting ${repo}:${{ github.sha }} to ${ECR_TAG}" - docker buildx imagetools create \ - -t "${REGISTRY}/${repo}:${ECR_TAG}" \ - "${REGISTRY}/${repo}:${{ github.sha }}" + if [ "$repo" = "$APP_REPO" ]; then + bash .github/scripts/promote-app-image.sh "$REGISTRY" "$APP_REPO" "$GITHUB_SHA" "$ECR_TAG" + else + docker buildx imagetools create \ + -t "${REGISTRY}/${repo}:${ECR_TAG}" \ + "${REGISTRY}/${repo}:${{ github.sha }}" + fi done + # Main/staging: promote the parked Trigger.dev version after observing the ECS + # traffic cutover (CodeDeploy AllowTraffic on every target). The image retag + # triggers the ECS pipeline; this job correlates it via the digest + retag epoch + # (rejecting a stale execution reusing the digest) and promotes at cutover. + # Skipped when promote-images skipped the tag move (stale run) — tasks then + # correctly stay on the old version. If the app deploy fails or never cuts over, + # promote never fires and this job fails visibly. + promote-trigger: + name: Promote Trigger.dev + needs: [promote-images, deploy-trigger] + # Explicit results also suppress skip propagation from optional ancestors. + if: >- + !cancelled() && + github.event_name == 'push' && + (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') && + needs.promote-images.result == 'success' && + needs.deploy-trigger.result == 'success' && + needs.promote-images.outputs.promoted == 'true' + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} + # Must exceed the poll script's OVERALL_TIMEOUT (70 min, covering a prod deploy + # queued behind a ~50-min bake) PLUS runner setup + the final promote step, so + # the Actions timeout never kills the job before the script's own deadline. + timeout-minutes: 90 + permissions: + contents: read + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.4.1 + + - name: Cache Bun dependencies + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 + with: + path: | + ~/.bun/install/cache + node_modules + **/node_modules + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install dependencies + run: bun install --frozen-lockfile --ignore-scripts + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6 + with: + role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }} + aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }} + # The poll can run up to ~70 min (prod deploy queued behind a bake), which + # outlasts the default 1h session. Hold the session for the full job so AWS + # calls don't start failing mid-poll. Requires the deploy role's + # MaxSessionDuration to be >= this value (roles are managed outside the repo). + role-duration-seconds: 5400 + + # An unchanged tag may belong to a failed or still-running earlier deploy. + # Verify its latest cutover rather than treating tag equality as success. + - name: Wait for ECS traffic cutover + env: + APP_IMAGE_CHANGED: ${{ needs.promote-images.outputs.app_image_changed }} + DIGEST: ${{ needs.promote-images.outputs.app_image_digest }} + PIPELINE: sim-${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}-us-east-1-app-deployment + RETAG_EPOCH: ${{ needs.promote-images.outputs.retag_epoch }} + run: | + set -eo pipefail + case "$APP_IMAGE_CHANGED" in + true) ;; + false) RETAG_EPOCH=0 ;; + *) echo "ERROR: invalid app image change metadata" >&2; exit 1 ;; + esac + bash .github/scripts/wait-for-ecs-cutover.sh "$PIPELINE" "$DIGEST" "$RETAG_EPOCH" + + - name: Promote Trigger.dev version + working-directory: ./apps/sim + env: + TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} + TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }} + TRIGGER_ENV: ${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }} + VERSION: ${{ needs.deploy-trigger.outputs.version }} + run: | + set -eo pipefail + if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then + echo "ERROR: TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2 + exit 1 + fi + if [ -z "$VERSION" ]; then + echo "ERROR: no deployed version passed from deploy-trigger" >&2 + exit 1 + fi + echo "Promoting Trigger.dev version $VERSION ($TRIGGER_ENV)" + bunx trigger.dev@4.5.12 promote "$VERSION" --env "$TRIGGER_ENV" + # Build ARM64 images for GHCR (main branch only, runs in parallel with # tests). Pushes only the immutable sha tag — latest-arm64/version-arm64 # are applied by create-ghcr-manifests after the gate, so a failing run diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index e9f77ac602c..61bbfdb56fd 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -363,6 +363,9 @@ jobs: - name: Lint code run: bun run lint:check + - name: Test Trigger deployment gates + run: python3 .github/scripts/test-trigger-deploy.py + # Every zero-argument `check:*` script, run concurrently. The list is derived in # scripts/run-audits.ts, which also writes the per-audit timing table to the job # summary and annotates failures. Audits needing a base ref stay separate below. From 9cfa04966d40b972244883ad26eeb28dc5a4bfed Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 16 Sep 2026 16:20:57 -0700 Subject: [PATCH 28/43] fix(search): complete sync failure diagnostic context (#7898) --- ...knowledge-connector-directory-sync.test.ts | 78 +++++++++++++++++++ .../knowledge-connector-directory-sync.ts | 14 +++- .../knowledge/connectors/connector-error.ts | 3 +- .../connectors/external-group-sync.test.ts | 29 ++++++- .../connectors/external-group-sync.ts | 14 +++- .../document-processing-source.test.ts | 59 ++++++++++++++ apps/sim/lib/knowledge/documents/service.ts | 20 ++++- 7 files changed, 207 insertions(+), 10 deletions(-) create mode 100644 apps/sim/background/knowledge-connector-directory-sync.test.ts diff --git a/apps/sim/background/knowledge-connector-directory-sync.test.ts b/apps/sim/background/knowledge-connector-directory-sync.test.ts new file mode 100644 index 00000000000..d67a8fa6a02 --- /dev/null +++ b/apps/sim/background/knowledge-connector-directory-sync.test.ts @@ -0,0 +1,78 @@ +/** @vitest-environment node */ +import { DrizzleQueryError } from 'drizzle-orm/errors' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { refresh } = vi.hoisted(() => ({ refresh: vi.fn() })) +vi.mock('@/lib/knowledge/connectors/external-group-sync', () => ({ + refreshConnectorDirectory: refresh, +})) + +import { executeDirectorySyncJob } from '@/background/knowledge-connector-directory-sync' +import { GoogleDriveApiError } from '@/connectors/google-drive/google-drive-errors' +import { ConnectorDirectoryError } from '@/connectors/source-error' + +const PAYLOAD = { connectorId: 'connector-1', requestId: 'request-1' } + +describe('directory sync worker diagnostics', () => { + beforeEach(() => vi.clearAllMocks()) + + it('preserves successful directory outcomes', async () => { + refresh.mockResolvedValueOnce('refreshed') + await expect(executeDirectorySyncJob(PAYLOAD)).resolves.toEqual({ outcome: 'refreshed' }) + expect(refresh).toHaveBeenCalledWith('connector-1', 'request-1') + }) + + it('includes the Google operation and reason in the final task failure', async () => { + refresh.mockRejectedValueOnce( + new ConnectorDirectoryError('private group detail', { + cause: new GoogleDriveApiError(403, ['forbidden'], 'directory.members.list'), + }) + ) + const error = await executeDirectorySyncJob(PAYLOAD).catch((error: unknown) => error) + expect(error).toMatchObject({ + message: + 'Directory permission sync failed (HTTP 403). Operation: directory.members.list. Google reason: forbidden. Group membership could not be fully verified.', + }) + expect(error).not.toHaveProperty('cause') + expect(String(error)).not.toContain('private') + }) + + it('preserves database codes without exposing the raw driver cause to Trigger', async () => { + refresh.mockRejectedValueOnce( + new DrizzleQueryError( + 'select private SQL', + ['private value'], + Object.assign(new Error('private driver detail'), { code: '57014' }) + ) + ) + const error = await executeDirectorySyncJob(PAYLOAD).catch((error: unknown) => error) + expect(error).toMatchObject({ message: 'Database request failed (SQLSTATE 57014).' }) + expect(error).not.toHaveProperty('cause') + expect(String(error)).not.toContain('private') + }) + + it('preserves unclassified failures', async () => { + const error = new Error('unexpected failure') + refresh.mockRejectedValueOnce(error) + await expect(executeDirectorySyncJob(PAYLOAD)).rejects.toBe(error) + }) + + it('retains the database code when a directory refresh wraps the driver failure', async () => { + refresh.mockRejectedValueOnce( + new ConnectorDirectoryError('private wrapper', { + cause: new DrizzleQueryError( + 'select private SQL', + ['private value'], + Object.assign(new Error('private driver detail'), { code: '57014' }) + ), + }) + ) + const error = await executeDirectorySyncJob(PAYLOAD).catch((error: unknown) => error) + expect(error).toMatchObject({ + message: + 'Directory permission sync failed. Error code: 57014. Group membership could not be fully verified.', + }) + expect(error).not.toHaveProperty('cause') + expect(String(error)).not.toContain('private') + }) +}) diff --git a/apps/sim/background/knowledge-connector-directory-sync.ts b/apps/sim/background/knowledge-connector-directory-sync.ts index f36bb618f44..e067d6a620d 100644 --- a/apps/sim/background/knowledge-connector-directory-sync.ts +++ b/apps/sim/background/knowledge-connector-directory-sync.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { task } from '@trigger.dev/sdk' +import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' import { assertDirectorySyncPayload, DIRECTORY_SYNC_CONCURRENCY, @@ -14,9 +15,16 @@ const logger = createLogger('TriggerKnowledgeConnectorDirectorySync') export async function executeDirectorySyncJob(payload: unknown) { const { connectorId, requestId } = assertDirectorySyncPayload(payload) logger.info(`[${requestId}] Starting directory refresh: ${connectorId}`) - const outcome = await refreshConnectorDirectory(connectorId, requestId) - logger.info(`[${requestId}] Directory refresh finished`, { connectorId, outcome }) - return { outcome } + try { + const outcome = await refreshConnectorDirectory(connectorId, requestId) + logger.info(`[${requestId}] Directory refresh finished`, { connectorId, outcome }) + return { outcome } + } catch (error) { + const diagnostic = getConnectorFailureDiagnostic(error) + if (!diagnostic) throw error + logger.error(`[${requestId}] Directory refresh failed`, { connectorId, diagnostic }) + throw new Error(diagnostic.message) + } } export const knowledgeConnectorDirectorySync = task({ diff --git a/apps/sim/lib/knowledge/connectors/connector-error.ts b/apps/sim/lib/knowledge/connectors/connector-error.ts index 6c7c09b3cc6..737903da280 100644 --- a/apps/sim/lib/knowledge/connectors/connector-error.ts +++ b/apps/sim/lib/knowledge/connectors/connector-error.ts @@ -125,13 +125,14 @@ export function getConnectorFailureDiagnostic(error: unknown): ConnectorFailureD const context = sourceError?.diagnostic if (directoryError) { const status = diagnostic?.status ? ` (HTTP ${diagnostic.status})` : '' + const code = diagnostic?.code ? ` Error code: ${diagnostic.code}.` : '' const reason = context?.reasons.length ? ` Google reason: ${context.reasons.join(', ')}.` : '' return { ...diagnostic, ...context, category: diagnostic?.category ?? 'directory', phase: 'directory', - message: `Directory permission sync failed${status}.${context ? ` Operation: ${context.operation}.` : ''}${reason} Group membership could not be fully verified.`, + message: `Directory permission sync failed${status}.${context ? ` Operation: ${context.operation}.` : ''}${reason}${code} Group membership could not be fully verified.`, } } if (!diagnostic || !context) return diagnostic diff --git a/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts b/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts index 90ae45c85c4..5369a251407 100644 --- a/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts +++ b/apps/sim/lib/knowledge/connectors/external-group-sync.test.ts @@ -4,6 +4,7 @@ import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' +import { GoogleDriveApiError } from '@/connectors/google-drive/google-drive-errors' import type { ConnectorDirectory } from '@/connectors/types' const { mockResolveTokenUserId, mockResolveToken, mockOpenDirectory, mockAvailability } = @@ -254,7 +255,8 @@ describe('refreshConnectorDirectory', () => { ) expect(dbChainMockFns.set).toHaveBeenCalledWith( expect.objectContaining({ - lastSyncError: 'Directory refresh failed: 403', + lastSyncError: + 'Directory refresh failed: Directory permission sync failed. Group membership could not be fully verified.', }) ) expect(dbChainMockFns.delete).not.toHaveBeenCalled() @@ -274,6 +276,31 @@ describe('refreshConnectorDirectory', () => { expect(dbChainMockFns.set.mock.calls.some(([value]) => 'lastSyncedAt' in value)).toBe(false) }) + it('persists the nested Google reason for scheduled directory failures', async () => { + queueTableRows(schemaMock.knowledgeConnector, [connectorRow()]) + const providerError = new GoogleDriveApiError(403, ['forbidden'], 'directory.members.list') + mockOpenDirectory.mockResolvedValue( + directory({ listGroupMembers: vi.fn().mockRejectedValue(providerError) }) + ) + + const failure = await refreshConnectorDirectory('connector-1', 'req-1').catch( + (error: unknown) => error + ) + expect(getConnectorFailureDiagnostic(failure)).toMatchObject({ + status: 403, + operation: 'directory.members.list', + reasons: ['forbidden'], + phase: 'directory', + }) + expect(dbChainMockFns.set).toHaveBeenCalledWith( + expect.objectContaining({ + lastSyncError: + 'Directory refresh failed: Directory permission sync failed (HTTP 403). Operation: directory.members.list. Google reason: forbidden. Group membership could not be fully verified.', + }) + ) + expect(dbChainMockFns.set.mock.calls.some(([value]) => 'lastSyncedAt' in value)).toBe(false) + }) + it('clears a previous directory error after a successful refresh', async () => { queueTableRows(schemaMock.knowledgeConnector, [ connectorRow({ lastSyncError: 'Directory refresh failed: 403' }), diff --git a/apps/sim/lib/knowledge/connectors/external-group-sync.ts b/apps/sim/lib/knowledge/connectors/external-group-sync.ts index 5fbfc7c0bff..5ea76ba69c0 100644 --- a/apps/sim/lib/knowledge/connectors/external-group-sync.ts +++ b/apps/sim/lib/knowledge/connectors/external-group-sync.ts @@ -28,6 +28,7 @@ import { resolveConnectorTokenUserId, syncContextForToken, } from '@/lib/knowledge/connectors/access-token' +import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' import { RUNNABLE_CONNECTOR_STATUSES } from '@/lib/knowledge/connectors/sync-lock' import { isRateLimitError } from '@/lib/knowledge/documents/utils' import { CONNECTOR_REGISTRY } from '@/connectors/registry.server' @@ -189,11 +190,13 @@ export async function syncExternalDirectoryGroups(input: { if (isRateLimitError(error)) throw error keptStale += 1 firstError ??= toError(error) + const diagnostic = getConnectorFailureDiagnostic(error) logger.warn('Keeping last-known-good membership for a group that failed to enumerate', { workspaceId, providerId, externalGroupId: group.id, - error: getErrorMessage(error), + error: diagnostic?.message ?? getErrorMessage(error), + diagnostic, }) continue } @@ -378,10 +381,12 @@ export async function refreshMirroredDirectory(input: { }) return result.skipped ? 'skipped' : 'refreshed' } catch (error) { + const diagnostic = getConnectorFailureDiagnostic(error) logger.error('Directory refresh failed; serving last-known-good group membership', { workspaceId, connector: connectorConfig.id, - error: getErrorMessage(error), + error: diagnostic?.message ?? getErrorMessage(error), + diagnostic, }) throw new ConnectorDirectoryError(`${DIRECTORY_ERROR_PREFIX}${getErrorMessage(error)}`, { cause: error, @@ -506,7 +511,10 @@ export async function refreshConnectorDirectory( } return outcome } catch (error) { - await recordError(getErrorMessage(error)) + const diagnostic = getConnectorFailureDiagnostic(error) + await recordError( + diagnostic ? `${DIRECTORY_ERROR_PREFIX}${diagnostic.message}` : getErrorMessage(error) + ) throw error } } diff --git a/apps/sim/lib/knowledge/documents/document-processing-source.test.ts b/apps/sim/lib/knowledge/documents/document-processing-source.test.ts index a84e8fa96d5..73ff94ee27d 100644 --- a/apps/sim/lib/knowledge/documents/document-processing-source.test.ts +++ b/apps/sim/lib/knowledge/documents/document-processing-source.test.ts @@ -746,6 +746,65 @@ describe('processDocumentAsync write guards', () => { expect(guardForStatusWrite('failed')).toBeDefined() }) + it('records the failed embedding batch without exposing SQL, content or vectors', async () => { + armProviderSource() + dbChainMockFns.limit.mockResolvedValueOnce([{ id: 'document-1' }]) + mockProcessDocument.mockResolvedValueOnce({ + chunks: [{ text: 'private-content', metadata: { startIndex: 0, endIndex: 15 } }], + metadata: { chunkCount: 1, tokenCount: 3, characterCount: 15 }, + }) + mockGenerateEmbeddings.mockResolvedValueOnce({ + embeddings: [[0.123456789]], + billableTokens: 0, + modelName: 'text-embedding-3-small', + pricingId: 'text-embedding-3-small', + }) + const databaseError = new DrizzleQueryError( + 'insert private SQL', + ['private-content', [0.123456789]], + Object.assign(new Error('canceling statement due to statement timeout'), { code: '57014' }) + ) + dbChainMockFns.values.mockRejectedValueOnce(databaseError) + + await expect( + processDocumentAsync( + 'knowledge-base-1', + 'document-1', + { + filename: 'a.txt', + fileUrl: 'https://example.com/a.txt', + fileSize: 15, + mimeType: 'text/plain', + }, + {}, + BILLING_ATTRIBUTION + ) + ).rejects.toBe(databaseError) + + expect(mockLogError).toHaveBeenCalledWith('[document-1] Failed to insert embedding batch', { + knowledgeBaseId: 'knowledge-base-1', + operation: 'embedding.insert', + batchNumber: 1, + batchSize: 1, + totalChunks: 1, + embeddingModel: 'text-embedding-3-small', + embeddingDimensions: 1536, + elapsedMs: expect.any(Number), + diagnostic: { + category: 'database', + code: '57014', + message: 'Database request failed (SQLSTATE 57014).', + }, + }) + const logs = JSON.stringify(mockLogError.mock.calls) + expect(logs).not.toContain('private') + expect(logs).not.toContain('0.123456789') + expect(guardForStatusWrite('failed')).toBeDefined() + expect( + dbChainMockFns.set.mock.calls.some(([value]) => value.processingStatus === 'completed') + ).toBe(false) + }) + it('accepts a legacy queuedAt-only payload only while the row has no token', async () => { dbChainMockFns.limit .mockResolvedValueOnce([PERSISTED_CONTEXT]) diff --git a/apps/sim/lib/knowledge/documents/service.ts b/apps/sim/lib/knowledge/documents/service.ts index efc0e9e9fda..50699895060 100644 --- a/apps/sim/lib/knowledge/documents/service.ts +++ b/apps/sim/lib/knowledge/documents/service.ts @@ -1889,9 +1889,25 @@ export async function processDocumentAsync( } logger.info(`[${documentId}] Inserting ${embeddingRecords.length} embeddings`) - for (const batch of batches) { + for (const [batchIndex, batch] of batches.entries()) { signal.throwIfAborted() - await tx.insert(embedding).values(batch) + const insertStartedAt = Date.now() + try { + await tx.insert(embedding).values(batch) + } catch (error) { + logger.error(`[${documentId}] Failed to insert embedding batch`, { + knowledgeBaseId, + operation: 'embedding.insert', + batchNumber: batchIndex + 1, + batchSize: batch.length, + totalChunks: embeddingRecords.length, + embeddingModel: kbEmbeddingModel, + embeddingDimensions: kbEmbedding.dimensions, + elapsedMs: Date.now() - insertStartedAt, + diagnostic: getConnectorFailureDiagnostic(error), + }) + throw error + } } const provenanceRecords = embeddingRecords.flatMap((record, index) => { const provenance = chunkProvenances[index] From 6be9008d4b872730003e9f928f2b08258bdca077 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 16 Sep 2026 16:49:56 -0700 Subject: [PATCH 29/43] fix(search): retain permission sync failures and clarify progress (#7897) * fix(search): retain permission sync failures and clarify progress * docs(search): explain permission warnings and sync continuation * fix(search): report rejected permission grants as incomplete * fix(search): distinguish member failures from continuation * fix(testing): include member sync failure columns in schema mocks * fix(search): retain central indexing dispatch warnings --- apps/docs/content/docs/search/confluence.mdx | 3 + apps/docs/content/docs/search/jira.mdx | 2 + .../organization-search-status.test.ts | 1 + .../organization-search-status.ts | 8 +- .../connector-sync-history.tsx | 42 +- .../connectors-section.test.tsx | 65 +- .../connectors/confluence/permissions.test.ts | 53 + apps/sim/connectors/confluence/permissions.ts | 17 +- apps/sim/hooks/queries/kb/connectors.test.ts | 32 + apps/sim/hooks/queries/kb/connectors.ts | 2 +- .../lib/api/contracts/knowledge/connectors.ts | 16 +- .../organization-search-overview.test.ts | 47 +- .../organization-search-overview.ts | 34 +- .../connectors/listing-checkpoint.test.ts | 16 +- .../connectors/listing-checkpoint.ts | 3 + .../member-sync-engine.integration.test.ts | 13 + .../connectors/member-sync-engine.ts | 4 + .../connectors/sync-content-pass.test.ts | 45 +- .../knowledge/connectors/sync-content-pass.ts | 17 +- .../knowledge/connectors/sync-engine.test.ts | 180 +- .../lib/knowledge/connectors/sync-engine.ts | 35 +- .../lib/knowledge/connectors/sync-limits.ts | 3 + .../0354_member_sync_failure_counts.sql | 2 + .../db/migrations/meta/0354_snapshot.json | 27242 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 3 + packages/testing/src/mocks/schema.mock.ts | 2 + 27 files changed, 27809 insertions(+), 85 deletions(-) create mode 100644 packages/db/migrations/0354_member_sync_failure_counts.sql create mode 100644 packages/db/migrations/meta/0354_snapshot.json diff --git a/apps/docs/content/docs/search/confluence.mdx b/apps/docs/content/docs/search/confluence.mdx index 5d63dbe45b7..ad8475e2b56 100644 --- a/apps/docs/content/docs/search/confluence.mdx +++ b/apps/docs/content/docs/search/confluence.mdx @@ -132,6 +132,8 @@ Open **Settings → Sources → Confluence**, then a source's **Documents**, **S Syncing runs automatically. Admins can use **Sync now** for an immediate update, **Pause syncing** to stop scheduled syncs, or **Resume syncing** to restart them. Successful manual syncs have a one-minute cooldown; failed syncs can be retried immediately. +In **Sync history**, **Continuing** means a healthy listing needs another batch. **Partial** means the sync did not fully succeed; read the accompanying notice. If permissions could not be verified, the last successful sync time stays unchanged and documents without verified access remain hidden from Search. + ## Troubleshooting | Problem | What to check | @@ -140,6 +142,7 @@ Syncing runs automatically. Admins can use **Sync now** for an immediate update, | Space picker is empty or fails | Check the domain, account's space access, and `read:space:confluence` scope. Manual space keys are also supported. | | Service-account validation fails | Check token expiry, site, Confluence app access, and the full scope list above, including `read:confluence-user`. | | Content syncs but Search is empty | Connect your personal Confluence identity. Check permission/directory sync errors and group-read scopes. | +| **Some permissions could not be verified** | Open the source's **Sync history**. Check the service account's space, page, and directory access. If access is correct and the warning persists, ask your operator to inspect the connector run's permission errors. Do not broaden sharing to clear the warning. | | A new page, blog post, or label is missing | Confluence search can take time to update. Once the content appears in Confluence search with the selected label, sync again. | | A restricted page is missing | Both your account and the crawling account need access to the page and its ancestors. | | Embedded content is missing | Index the referenced page separately; remote macro output is excluded. | diff --git a/apps/docs/content/docs/search/jira.mdx b/apps/docs/content/docs/search/jira.mdx index 9faacf30110..2b9c3c6ee56 100644 --- a/apps/docs/content/docs/search/jira.mdx +++ b/apps/docs/content/docs/search/jira.mdx @@ -90,6 +90,8 @@ After an admin approves Jira, a teammate can select **Connect** on the Jira row Admins open **Settings → Sources → Jira**, then a connection’s **Documents**, **Settings**, or **Sync history**. Metadata tags include issue type, status, priority, labels, assignee, and last updated. +In **Sync history**, **Continuing** means a healthy listing needs another batch. **Partial** means some work did not succeed; the account counts and notice identify what needs attention. + Teammates use the configured site and projects without entering them again. **Additional connection required** means another configured selection needs authorization; select **Connect**. **Reconnect** is for an account whose authorization needs renewing. Invite teammates through **Settings → Members → Invite** or SSO, then have them connect Jira through **Integrations**. **Settings → Sources → People**, filter by **Jira**, then select **Request connections** only requests a provider connection; it does not invite people to the organization. diff --git a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-search-status.test.ts b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-search-status.test.ts index 90b069f5f44..90cb38491ef 100644 --- a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-search-status.test.ts +++ b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-search-status.test.ts @@ -29,6 +29,7 @@ describe('organization source status labels', () => { ['sync_failed', 'Sync failed'], ['account_sync_incomplete', 'Some accounts are not up to date'], ['document_indexing_failed', 'Some documents failed to index'], + ['permission_sync_incomplete', 'Some permissions could not be verified'], ] as const)('describes %s and keeps concurrent recovery visible', (issue, label) => { expect(organizationSearchStatusLabel({ ...provider, status: 'needs_attention', issue })).toBe( label diff --git a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-search-status.ts b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-search-status.ts index 5988b08ce56..d4eed45ee37 100644 --- a/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-search-status.ts +++ b/apps/sim/app/o/[organizationId]/settings/components/integrations/organization-search-status.ts @@ -16,9 +16,11 @@ export function organizationSearchStatusLabel(provider: OrganizationSearchProvid const error = provider.issue === 'account_sync_incomplete' ? 'Some accounts are not up to date' - : provider.issue === 'document_indexing_failed' - ? 'Some documents failed to index' - : 'Sync failed' + : provider.issue === 'permission_sync_incomplete' + ? 'Some permissions could not be verified' + : provider.issue === 'document_indexing_failed' + ? 'Some documents failed to index' + : 'Sync failed' return provider.isSyncing ? `Indexing · ${error}` : error } return STATUS_LABELS[provider.status] diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connector-sync-history.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connector-sync-history.tsx index 838960d35f8..f13d50a22c7 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connector-sync-history.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connector-sync-history.tsx @@ -62,7 +62,7 @@ export function ConnectorSyncHistory({ ) } -type SyncLogState = 'running' | 'interrupted' | 'failed' | 'completed' | 'partial' +type SyncLogState = 'running' | 'interrupted' | 'failed' | 'completed' | 'partial' | 'continuing' const SYNC_LOG_LABELS: Record = { running: 'In progress…', @@ -70,6 +70,7 @@ const SYNC_LOG_LABELS: Record = { failed: 'Failed', completed: 'Completed', partial: 'Partial', + continuing: 'Continuing', } /** Reclaimed stale locks leave started log rows behind; both views use the engine's own TTL. */ @@ -92,9 +93,10 @@ interface SyncHistoryRowProps { startedAt: string state: SyncLogState description?: string + notice?: string | null } -function SyncHistoryRow({ startedAt, state, description }: SyncHistoryRowProps) { +function SyncHistoryRow({ startedAt, state, description, notice }: SyncHistoryRowProps) { return ( · {SYNC_LOG_LABELS[state]}} } - description={description} + description={[description, notice].filter(Boolean).join(' · ') || undefined} badge={ state === 'completed' ? undefined : ( {logs.map((log) => { - const state = getSyncLogState(log, CONNECTOR_SYNC_STALE_LOCK_TTL_MS, now) + const continuing = + log.status === 'partial' && + log.listedCount === null && + log.docsFailed === 0 && + !log.errorMessage + const state = continuing + ? 'continuing' + : getSyncLogState(log, CONNECTOR_SYNC_STALE_LOCK_TTL_MS, now) const changes = [ log.docsAdded > 0 && `${log.docsAdded} added`, log.docsUpdated > 0 && `${log.docsUpdated} updated`, @@ -150,11 +159,13 @@ export function SyncHistory({ logs, isLoading }: SyncHistoryProps) { key={log.id} startedAt={log.startedAt} state={state} + notice={state === 'failed' ? undefined : log.errorMessage} description={ state === 'failed' ? (log.errorMessage ?? undefined) - : state === 'completed' || state === 'partial' - ? changes || 'No changes' + : state === 'completed' || state === 'partial' || state === 'continuing' + ? changes || + (state === 'continuing' || log.errorMessage ? undefined : 'No changes') : undefined } /> @@ -193,17 +204,29 @@ function MemberSyncHistory({ logs, members, isLoading }: MemberSyncHistoryProps) No member sync history yet. ) : ( logs.map((log) => { - const state = getSyncLogState(log, MEMBER_SYNC_STALE_LOCK_TTL_MS, now) + const continuing = + log.status === 'partial' && + log.membersIncomplete > 0 && + log.membersFailed === 0 && + log.docsFailed === 0 && + log.processingDispatchFailed === 0 && + !log.errorMessage + const state = continuing + ? 'continuing' + : getSyncLogState(log, MEMBER_SYNC_STALE_LOCK_TTL_MS, now) const changes = [ log.docsAdded > 0 && `${log.docsAdded} added`, log.docsUpdated > 0 && `${log.docsUpdated} updated`, log.docsTombstoned + log.docsPurged > 0 && `${log.docsTombstoned + log.docsPurged} deleted`, + (log.docsFailed ?? 0) > 0 && `${log.docsFailed} failed`, + (log.processingDispatchFailed ?? 0) > 0 && + `${log.processingDispatchFailed} failed to queue`, ] .filter(Boolean) .join(' · ') const description = [ - changes || 'No changes', + changes || (continuing || log.errorMessage ? undefined : 'No changes'), log.membersFailed > 0 && `${log.membersFailed} ${log.membersFailed === 1 ? 'account' : 'accounts'} failed`, log.membersIncomplete > 0 && @@ -216,10 +239,11 @@ function MemberSyncHistory({ logs, members, isLoading }: MemberSyncHistoryProps) key={log.id} startedAt={log.startedAt} state={state} + notice={state === 'failed' ? undefined : log.errorMessage} description={ state === 'failed' ? (log.errorMessage ?? undefined) - : state === 'completed' || state === 'partial' + : state === 'completed' || state === 'partial' || state === 'continuing' ? description : undefined } diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.test.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.test.tsx index c8574915cbe..fa07965ebc2 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.test.tsx @@ -1063,6 +1063,41 @@ describe('shared connector sync history', () => { } ) + it.each([ + { docsFailed: 0, processingDispatchFailed: 0, continuing: true }, + { docsFailed: 1, processingDispatchFailed: 0, continuing: false }, + { docsFailed: 0, processingDispatchFailed: 1, continuing: false }, + { docsFailed: null, processingDispatchFailed: null, continuing: false }, + { docsFailed: undefined, processingDispatchFailed: undefined, continuing: false }, + ])('requires known healthy member counters for continuation: %j', (fields) => { + lifecycle.detail.current = { + memberSyncLogs: [ + { + ...makeLog({ status: 'partial' }), + membersCompleted: 1, + membersIncomplete: 1, + membersFailed: 0, + docsFailed: fields.docsFailed, + processingDispatchFailed: fields.processingDispatchFailed, + docsTombstoned: 0, + docsPurged: 0, + }, + ], + } + const container = renderComponent( + + ) + expect(container.textContent).toContain(fields.continuing ? 'Continuing' : 'Partial') + expect(container.textContent).not.toContain(fields.continuing ? 'Partial' : 'Continuing') + if (fields.continuing) expect(container.textContent).not.toContain('No changes') + if (fields.docsFailed) expect(container.textContent).toContain('1 failed') + if (fields.processingDispatchFailed) + expect(container.textContent).toContain('1 failed to queue') + }) + it('loads the member engine history rather than the content history', () => { lifecycle.detail.current = { syncLogs: [makeLog({ status: 'completed', docsAdded: 999 })], @@ -1129,13 +1164,37 @@ describe('SyncHistory', () => { expect(container.textContent).not.toContain('No changes') }) - it('renders a continued listing as partial with the work already completed', () => { - const container = render(makeLog({ status: 'partial', docsAdded: 3 })) - expect(container.textContent).toContain('Partial') + it('distinguishes a continued listing from a partial failure', () => { + const container = render(makeLog({ status: 'partial', docsAdded: 3, listedCount: null })) + expect(container.textContent).toContain('Continuing') expect(container.textContent).toContain('3 added') expect(container.textContent).not.toContain('In progress…') }) + it('keeps permission failures visible even when document processing succeeded', () => { + const container = render( + makeLog({ + status: 'partial', + listedCount: 4, + errorMessage: 'Some document permissions could not be verified.', + }) + ) + expect(container.textContent).toContain('Some document permissions could not be verified.') + expect(container.textContent).toContain('Partial') + expect(container.textContent).not.toContain('No changes') + expect(container.textContent).not.toContain('Continuing') + }) + + it.each([ + { docsFailed: 1, listedCount: null }, + { docsFailed: 0, listedCount: 4 }, + { docsFailed: 0 }, + ])('does not label failed, finished, or legacy partial logs as continuing: %j', (fields) => { + const container = render(makeLog({ status: 'partial', ...fields })) + expect(container.textContent).toContain('Partial') + expect(container.textContent).not.toContain('Continuing') + }) + it('keeps completion accessible without repeating decorative status on every row', () => { const log = makeLog({ status: 'completed', docsAdded: 3 }) const container = render(log) diff --git a/apps/sim/connectors/confluence/permissions.test.ts b/apps/sim/connectors/confluence/permissions.test.ts index 41558041873..1234a6cd4e6 100644 --- a/apps/sim/connectors/confluence/permissions.test.ts +++ b/apps/sim/connectors/confluence/permissions.test.ts @@ -284,6 +284,59 @@ describe('listSpaceReadPrincipals', () => { await expect(listSpaceReadPrincipals(CLOUD, 'token', 'space-1')).rejects.toThrow('403') }) + + it.each([ + '/wiki/api/v2/spaces/1/permissions?cursor=next', + '/wiki/api/v2/spaces/1/permissions?limit=250', + ])( + 'rejects a repeated or missing cursor without publishing partial permissions: %s', + async (next) => { + mockFetch + .mockResolvedValueOnce(jsonResponse({ results: [], _links: { next: '?cursor=next' } })) + .mockResolvedValueOnce(jsonResponse({ results: [], _links: { next } })) + + await expect(listSpaceReadPrincipals(CLOUD, 'token', 'space-1')).rejects.toThrow( + 'invalid or repeated space permissions continuation' + ) + expect(mockFetch).toHaveBeenCalledTimes(2) + } + ) + + it('rejects a cursor cycle rather than making a hundred repeated requests', async () => { + for (const cursor of ['first', 'second', 'first']) { + mockFetch.mockResolvedValueOnce( + jsonResponse({ results: [], _links: { next: `?cursor=${cursor}` } }) + ) + } + await expect(listSpaceReadPrincipals(CLOUD, 'token', 'space-1')).rejects.toThrow('repeated') + expect(mockFetch).toHaveBeenCalledTimes(3) + }) + + it('rejects a malformed collection instead of treating it as a verified empty grant', async () => { + mockFetch.mockResolvedValueOnce(jsonResponse({})) + await expect(listSpaceReadPrincipals(CLOUD, 'token', 'space-1')).rejects.toThrow( + 'invalid space permissions' + ) + }) + + it('keeps the request bound for a provider that keeps issuing distinct continuations', async () => { + let page = 0 + mockFetch.mockImplementation(async () => + jsonResponse({ + results: [ + { + principal: { type: 'user', id: 'reader' }, + operation: { key: 'read', targetType: 'space' }, + }, + ], + _links: { next: `?cursor=${++page}` }, + }) + ) + await expect(listSpaceReadPrincipals(CLOUD, 'token', 'space-1')).rejects.toThrow( + 'exceeded 100 pages (100 entries)' + ) + expect(mockFetch).toHaveBeenCalledTimes(100) + }) }) describe('getReadRestriction', () => { diff --git a/apps/sim/connectors/confluence/permissions.ts b/apps/sim/connectors/confluence/permissions.ts index e354fc80ba4..89f98371684 100644 --- a/apps/sim/connectors/confluence/permissions.ts +++ b/apps/sim/connectors/confluence/permissions.ts @@ -78,6 +78,7 @@ async function getJson( */ async function drainV2(url: string, accessToken: string, what: string): Promise { const items: T[] = [] + const cursors = new Set() let cursor: string | undefined for (let page = 0; page < MAX_PAGES; page += 1) { const query = new URLSearchParams({ limit: String(PAGE_SIZE) }) @@ -86,11 +87,19 @@ async function drainV2(url: string, accessToken: string, what: string): Promi `${url}?${query.toString()}`, accessToken ) - items.push(...(body.results ?? [])) - cursor = extractCursor(body._links?.next) - if (!cursor) return items + if (!Array.isArray(body.results)) { + throw new Error(`Confluence returned invalid ${what}`) + } + items.push(...body.results) + const next = body._links?.next + if (!next) return items + cursor = extractCursor(next) + if (!cursor || cursors.has(cursor)) { + throw new Error(`Confluence returned an invalid or repeated ${what} continuation`) + } + cursors.add(cursor) } - throw new Error(`Confluence ${what} exceeded ${MAX_PAGES} pages`) + throw new Error(`Confluence ${what} exceeded ${MAX_PAGES} pages (${items.length} entries)`) } /** diff --git a/apps/sim/hooks/queries/kb/connectors.test.ts b/apps/sim/hooks/queries/kb/connectors.test.ts index ae98455db5a..e65012d3802 100644 --- a/apps/sim/hooks/queries/kb/connectors.test.ts +++ b/apps/sim/hooks/queries/kb/connectors.test.ts @@ -45,6 +45,7 @@ import { } from '@/lib/api/contracts/knowledge' import { type ConnectorDetailData, + type OrganizationSearchOverview, readSearchIndexContract, } from '@/lib/api/contracts/knowledge/connectors' import { MAX_KNOWLEDGE_CONNECTOR_DOCUMENT_PAGE_SIZE } from '@/lib/knowledge/constants' @@ -55,6 +56,7 @@ import { useConnectorDetail, useConnectorDocuments, useConnectorList, + useOrganizationSearchOverview, useSearchIndex, useSearchSources, useTriggerSync, @@ -159,6 +161,36 @@ describe('isConnectorSyncingOrPending', () => { ) }) +describe('organization overview polling', () => { + it.each([ + { isSyncing: true, hasPendingSync: false, polling: true }, + { isSyncing: false, hasPendingSync: true, polling: true }, + { isSyncing: false, hasPendingSync: false, polling: false }, + { isSyncing: false, hasPendingSync: undefined, polling: false }, + ])('polls unfinished work across worker handoffs: %j', ({ polling, ...state }) => { + useOrganizationSearchOverview('organization-1') + const { refetchInterval } = capturedQueryOptions() + const interval = refetchInterval({ + state: { + data: { + providers: [ + { + connectorType: 'confluence', + approved: true, + sourceCount: 1, + status: 'active', + issue: null, + ...state, + }, + ], + }, + }, + }) + if (polling) expect(interval).toBeGreaterThan(0) + else expect(interval).toBe(false) + }) +}) + describe('useConnectorList polling', () => { beforeEach(() => { vi.clearAllMocks() diff --git a/apps/sim/hooks/queries/kb/connectors.ts b/apps/sim/hooks/queries/kb/connectors.ts index fa507b4d24f..bdfb1fb6a0a 100644 --- a/apps/sim/hooks/queries/kb/connectors.ts +++ b/apps/sim/hooks/queries/kb/connectors.ts @@ -476,7 +476,7 @@ export function useOrganizationSearchOverview( enabled: Boolean(organizationId) && (options?.enabled ?? true), staleTime: CONNECTOR_LIST_STALE_TIME, refetchInterval: (query) => - query.state.data?.providers.some((provider) => provider.isSyncing) + query.state.data?.providers.some((provider) => provider.isSyncing || provider.hasPendingSync) ? SEARCH_SOURCE_SUMMARY_POLL_MS : false, }) diff --git a/apps/sim/lib/api/contracts/knowledge/connectors.ts b/apps/sim/lib/api/contracts/knowledge/connectors.ts index 48a084df89b..1580228c949 100644 --- a/apps/sim/lib/api/contracts/knowledge/connectors.ts +++ b/apps/sim/lib/api/contracts/knowledge/connectors.ts @@ -177,6 +177,8 @@ export const syncLogDataSchema = z docsUnchanged: z.number(), docsSkipped: z.number().int().nonnegative().default(0), docsFailed: z.number(), + /** Older responses omit this; null records an unfinished listing. */ + listedCount: z.number().int().nonnegative().nullable().optional(), errorMessage: z.string().nullable(), }) .passthrough() @@ -193,6 +195,9 @@ export const memberSyncLogDataSchema = z membersCompleted: z.number(), membersIncomplete: z.number(), membersFailed: z.number(), + /** Null for historical logs; absent from responses served by older deployments. */ + docsFailed: z.number().int().nonnegative().nullable().optional(), + processingDispatchFailed: z.number().int().nonnegative().nullable().optional(), docsListed: z.number(), docsAdded: z.number(), docsUpdated: z.number(), @@ -462,8 +467,17 @@ export const organizationSearchProviderSummarySchema = z.object({ approved: z.boolean(), sourceCount: z.number().int().nonnegative(), status: organizationSearchProviderStatusSchema, - issue: z.enum(['sync_failed', 'account_sync_incomplete', 'document_indexing_failed']).nullable(), + issue: z + .enum([ + 'sync_failed', + 'account_sync_incomplete', + 'document_indexing_failed', + 'permission_sync_incomplete', + ]) + .nullable(), isSyncing: z.boolean(), + /** Older servers omit the continuation signal during a rolling deployment. */ + hasPendingSync: z.boolean().optional(), }) export type OrganizationSearchProviderSummary = z.output< typeof organizationSearchProviderSummarySchema diff --git a/apps/sim/lib/knowledge/application/organization-search-overview.test.ts b/apps/sim/lib/knowledge/application/organization-search-overview.test.ts index 6bb20aa3796..2bf9db2a010 100644 --- a/apps/sim/lib/knowledge/application/organization-search-overview.test.ts +++ b/apps/sim/lib/knowledge/application/organization-search-overview.test.ts @@ -64,7 +64,9 @@ const health = { hasError: false, hasAccountError: false, hasDocumentError: false, + hasPermissionError: false, hasIndexing: false, + hasPendingSync: false, hasWaiting: false, hasUnstarted: false, } @@ -90,11 +92,25 @@ describe('organization Search administration overview', () => { mocks.availability.mockResolvedValue({ memberScoped, sourceMirrored: true }) const result = await readOrganizationSearchOverview.execute({ principal, input }) expect(result.providers).toEqual([ - { connectorType, approved: true, sourceCount: 0, status, issue: null, isSyncing: false }, + { + connectorType, + approved: true, + sourceCount: 0, + status, + issue: null, + isSyncing: false, + hasPendingSync: false, + }, ]) } ) it.each([ + { + hasPermissionError: true, + hasAccountError: false, + hasDocumentError: false, + issue: 'permission_sync_incomplete', + }, { hasAccountError: true, hasDocumentError: false, issue: 'account_sync_incomplete' }, { hasAccountError: false, hasDocumentError: true, issue: 'document_indexing_failed' }, { hasAccountError: false, hasDocumentError: false, issue: 'sync_failed' }, @@ -129,11 +145,34 @@ describe('organization Search administration overview', () => { status: 'needs_attention', issue: 'sync_failed', isSyncing: true, + hasPendingSync: false, }, ]) expect(organizationSearchOverviewSchema.parse(result)).toEqual(result) }) + it('includes member document and dispatch failures in provider health queries', async () => { + queueTableRows(member, [{ role: 'admin' }]) + queueTableRows(knowledgeConnector, [{ ...health }]) + await readOrganizationSearchOverview.execute({ principal, input }) + const selection = dbChainMockFns.select.mock.calls.find(([fields]) => fields?.hasError)?.[0] + const { params } = renderFragment(selection?.hasError) + expect(params).toContain('knowledgeConnectorMemberSyncLog.docsFailed') + expect(params).toContain('knowledgeConnectorMemberSyncLog.processingDispatchFailed') + expect(params).not.toContain(undefined) + }) + + it('keeps unfinished work observable without presenting an idle worker as indexing', async () => { + queueTableRows(member, [{ role: 'admin' }]) + queueTableRows(knowledgeConnector, [{ ...health, hasPendingSync: true, hasUnstarted: true }]) + const result = await readOrganizationSearchOverview.execute({ principal, input }) + expect(result.providers[0]).toMatchObject({ + status: 'needs_setup', + isSyncing: false, + hasPendingSync: true, + }) + }) + it.each(['admin', 'owner'])( 'allows a current %s and returns only operational facts', async (role) => { @@ -155,6 +194,7 @@ describe('organization Search administration overview', () => { status: 'active', issue: null, isSyncing: false, + hasPendingSync: false, }, { connectorType: 'gmail', @@ -163,6 +203,7 @@ describe('organization Search administration overview', () => { status: 'waiting_for_connections', issue: null, isSyncing: false, + hasPendingSync: false, }, { connectorType: 'github', @@ -171,6 +212,7 @@ describe('organization Search administration overview', () => { status: 'paused', issue: null, isSyncing: false, + hasPendingSync: false, }, ], }) @@ -223,6 +265,7 @@ describe('organization Search administration overview', () => { status: 'paused', issue: null, isSyncing: false, + hasPendingSync: false, }, ]) }) @@ -256,6 +299,7 @@ describe('organization Search administration overview', () => { status: 'paused', issue: null, isSyncing: false, + hasPendingSync: false, }, { connectorType: 'gmail', @@ -264,6 +308,7 @@ describe('organization Search administration overview', () => { status: 'paused', issue: null, isSyncing: false, + hasPendingSync: false, }, ]) }) diff --git a/apps/sim/lib/knowledge/application/organization-search-overview.ts b/apps/sim/lib/knowledge/application/organization-search-overview.ts index 97ebe9209cc..890fc5c5cb2 100644 --- a/apps/sim/lib/knowledge/application/organization-search-overview.ts +++ b/apps/sim/lib/knowledge/application/organization-search-overview.ts @@ -15,7 +15,10 @@ import { SOURCE_ACL_MAX_AGE_MS } from '@/lib/knowledge/access/freshness' import { defineAuthorizedKnowledgeUseCase } from '@/lib/knowledge/application/authorized-knowledge-use-case' import { resolveKnowledgeOwnerContext } from '@/lib/knowledge/application/contexts' import { knowledgeOperations } from '@/lib/knowledge/application/operations' -import { SOURCE_CONTENT_ERROR } from '@/lib/knowledge/connectors/sync-limits' +import { + SOURCE_CONTENT_ERROR, + SOURCE_PERMISSION_ERROR, +} from '@/lib/knowledge/connectors/sync-limits' import { MAX_SEARCH_SOURCE_PROVIDER_TYPES } from '@/lib/knowledge/constants' import { failedDocumentCondition } from '@/lib/knowledge/documents/processing-status' import { canConnectWithDefaults, SEARCH_SOURCE_TYPES } from '@/lib/sim-search/connectors' @@ -30,7 +33,9 @@ interface ProviderHealth { hasError: boolean hasAccountError: boolean hasDocumentError: boolean + hasPermissionError: boolean hasIndexing: boolean + hasPendingSync: boolean hasWaiting: boolean hasUnstarted: boolean } @@ -158,7 +163,10 @@ export const readOrganizationSearchOverview = defineAuthorizedKnowledgeUseCase({ const latestMemberRunHasError = sql`coalesce(( SELECT ${knowledgeConnectorMemberSyncLog.status} = 'failed' OR (${knowledgeConnectorMemberSyncLog.status} = 'partial' AND ( - ${knowledgeConnectorMemberSyncLog.membersFailed} > 0 OR NOT ${continuing} + ${knowledgeConnectorMemberSyncLog.membersFailed} > 0 + OR ${knowledgeConnectorMemberSyncLog.docsFailed} > 0 + OR ${knowledgeConnectorMemberSyncLog.processingDispatchFailed} > 0 + OR NOT ${continuing} )) FROM ${knowledgeConnectorMemberSyncLog} WHERE ${knowledgeConnectorMemberSyncLog.connectorId} = ${knowledgeConnector.id} @@ -205,17 +213,24 @@ export const readOrganizationSearchOverview = defineAuthorizedKnowledgeUseCase({ ))`, hasAccountError: sql`bool_or(NOT ${paused} AND ${knowledgeConnector.accessMode} = 'members' AND ${hasMemberError})`, hasDocumentError: sql`bool_or(NOT ${paused} AND ${hasDocumentsInState(failedDocumentCondition())})`, + hasPermissionError: sql`bool_or(NOT ${paused} AND ${knowledgeConnector.lastSyncError} = ${SOURCE_PERMISSION_ERROR})`, hasIndexing: sql`bool_or(NOT ${paused} AND (${knowledgeConnector.accessMode} <> 'members' OR ${hasActiveMembers} OR ${knowledgeConnector.credentialId} IS NOT NULL) AND ( ${knowledgeConnector.status} IN ('pending', 'syncing') - OR ${continuing} OR ${hasDocumentsInState(inArray(document.processingStatus, ['pending', 'processing']))} + OR ${hasDocumentsInState(inArray(document.processingStatus, ['pending', 'processing']))} OR (${knowledgeConnector.accessMode} = 'members' AND ( - ${knowledgeConnector.memberSyncStatus} IN ('pending', 'running') OR ${hasMemberFirstListing} + ${knowledgeConnector.memberSyncStatus} IN ('pending', 'running') )) ))`, hasWaiting: sql`bool_or(NOT ${paused} AND ${knowledgeConnector.accessMode} = 'members' AND NOT ${hasActiveMembers})`, - hasUnstarted: sql`bool_or(NOT ${paused} AND ${knowledgeConnector.accessMode} = 'admin' AND ${knowledgeConnector.lastSyncAt} IS NULL)`, + hasPendingSync: sql`bool_or(NOT ${paused} AND ( + ${continuing} OR (${knowledgeConnector.accessMode} = 'members' AND ${hasMemberFirstListing}) + ))`, + hasUnstarted: sql`bool_or(NOT ${paused} AND ( + (${knowledgeConnector.accessMode} = 'admin' AND ${knowledgeConnector.lastSyncAt} IS NULL) + OR (${knowledgeConnector.accessMode} = 'members' AND ${hasMemberFirstListing}) + ))`, }) .from(knowledgeConnector) .innerJoin(knowledgeBase, eq(knowledgeBase.id, knowledgeConnector.knowledgeBaseId)) @@ -276,11 +291,14 @@ export const readOrganizationSearchOverview = defineAuthorizedKnowledgeUseCase({ status === 'needs_attention' ? state?.hasAccountError ? ('account_sync_incomplete' as const) - : state?.hasDocumentError - ? ('document_indexing_failed' as const) - : ('sync_failed' as const) + : state?.hasPermissionError + ? ('permission_sync_incomplete' as const) + : state?.hasDocumentError + ? ('document_indexing_failed' as const) + : ('sync_failed' as const) : null, isSyncing: status !== 'paused' && Boolean(state?.hasIndexing), + hasPendingSync: status !== 'paused' && Boolean(state?.hasPendingSync), }, ] }), diff --git a/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts b/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts index bbe904f130b..dd8f5df65e4 100644 --- a/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts +++ b/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts @@ -1,4 +1,6 @@ /** @vitest-environment node */ + +import { omit } from '@sim/utils/object' import { describe, expect, it, vi } from 'vitest' import { beginListingCheckpoint, @@ -204,7 +206,12 @@ describe('durable connector listing checkpoints', () => { ) it('restarts an expired provider cursor once with a new generation', async () => { - const f = fixture({ ...checkpoint(), cursor: 'expired', listedCount: 700 }) + const f = fixture({ + ...checkpoint(), + cursor: 'expired', + listedCount: 700, + permissionFailures: true, + }) const error = new Error('expired') const databaseTime = new Date('2026-09-08T10:00:00Z') const getGenerationStartedAt = vi.fn(async () => databaseTime) @@ -222,7 +229,7 @@ describe('durable connector listing checkpoints', () => { expect(result.generationId).not.toBe('cycle-1') expect(result.startedAt).toBe(databaseTime.toISOString()) expect(getGenerationStartedAt).toHaveBeenCalledOnce() - expect(result).toMatchObject({ complete: true, listedCount: 1 }) + expect(result).toMatchObject({ complete: true, listedCount: 1, permissionFailures: false }) expect(f.listDocuments.mock.calls[1][2]).toBeUndefined() expect(f.processPage.mock.calls[0][1].generationId).toBe(result.generationId) }) @@ -254,6 +261,11 @@ describe('durable connector listing checkpoints', () => { expect(f.listDocuments).not.toHaveBeenCalled() }) + it('resumes older checkpoints without inventing permission failures', () => { + const legacy = omit(checkpoint(), ['permissionFailures']) + expect(readListingCheckpoint(legacy, fingerprint)).toMatchObject({ permissionFailures: false }) + }) + it('rejects checkpoints from a changed configuration or malformed serialized value', () => { expect(readListingCheckpoint(checkpoint(), fingerprint)).toEqual(checkpoint()) expect( diff --git a/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts b/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts index f44c1feade5..7ca43030d82 100644 --- a/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts +++ b/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts @@ -21,6 +21,7 @@ const checkpointSchema = z.object({ listedCount: z.number().int().nonnegative(), unsafe: z.boolean(), contentFailures: z.boolean().default(false), + permissionFailures: z.boolean().default(false), changeCursor: z .string() .max(512 * 1024) @@ -66,6 +67,7 @@ export function beginListingCheckpoint(input: { listedCount: 0, unsafe: false, contentFailures: false, + permissionFailures: false, changeCursor: input.changeCursor ?? null, incrementalSince: input.incrementalSince?.toISOString() ?? null, forceRehydrate: input.forceRehydrate ?? false, @@ -132,6 +134,7 @@ export async function runResumableListing(input: { listedCount: 0, unsafe: false, contentFailures: false, + permissionFailures: false, } await input.saveCheckpoint(checkpoint) cursors.clear() diff --git a/apps/sim/lib/knowledge/connectors/member-sync-engine.integration.test.ts b/apps/sim/lib/knowledge/connectors/member-sync-engine.integration.test.ts index 140e5c4e7d3..8f79f52ee6a 100644 --- a/apps/sim/lib/knowledge/connectors/member-sync-engine.integration.test.ts +++ b/apps/sim/lib/knowledge/connectors/member-sync-engine.integration.test.ts @@ -565,6 +565,9 @@ describe('member engine with a dedicated content credential', () => { mocks.get.mockRejectedValueOnce(new Error('Download interrupted')) const result = await run() expect(result.docsFailed).toBe(1) + expect(dbChainMockFns.set).toHaveBeenCalledWith( + expect.objectContaining({ status: 'partial', docsFailed: 1, processingDispatchFailed: 0 }) + ) expect(mocks.add).not.toHaveBeenCalled() expect(dbChainMockFns.set.mock.calls.some(([value]) => value.lastSyncAt instanceof Date)).toBe( false @@ -579,6 +582,16 @@ describe('member engine with a dedicated content credential', () => { expect(mocks.get.mock.calls[0][0]).toBe('service-token') }) + it('records processing dispatch failures separately from document failures', async () => { + const run = arrange() + mocks.dispatch.mockResolvedValue({ accepted: 0, failed: 1 }) + const result = await run() + expect(result.processingDispatch.failed).toBe(1) + expect(dbChainMockFns.set).toHaveBeenCalledWith( + expect.objectContaining({ status: 'partial', docsFailed: 0, processingDispatchFailed: 1 }) + ) + }) + it('keeps an interrupted forced crawl due instead of retaining its previous fresh watermark', async () => { const run = arrange({ contentFresh: true, forceContentRefresh: true }) mocks.list diff --git a/apps/sim/lib/knowledge/connectors/member-sync-engine.ts b/apps/sim/lib/knowledge/connectors/member-sync-engine.ts index 3357161f6d8..addef3346e4 100644 --- a/apps/sim/lib/knowledge/connectors/member-sync-engine.ts +++ b/apps/sim/lib/knowledge/connectors/member-sync-engine.ts @@ -1498,6 +1498,8 @@ async function completeMemberSync( membersCompleted: result.membersCompleted, membersIncomplete: result.membersIncomplete, membersFailed: result.membersFailed, + docsFailed: result.docsFailed, + processingDispatchFailed: result.processingDispatch.failed, docsListed: result.docsListed, docsAdded: result.docsAdded, docsUpdated: result.docsUpdated, @@ -1549,6 +1551,8 @@ async function failMemberSyncLog(runId: string, result: MemberSyncResult, errorM membersCompleted: result.membersCompleted, membersIncomplete: result.membersIncomplete, membersFailed: result.membersFailed, + docsFailed: result.docsFailed, + processingDispatchFailed: result.processingDispatch.failed, docsListed: result.docsListed, docsAdded: result.docsAdded, docsUpdated: result.docsUpdated, diff --git a/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts b/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts index d4638298b69..798459d4d88 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts @@ -13,7 +13,10 @@ import { type ListingCheckpoint, } from '@/lib/knowledge/connectors/listing-checkpoint' import { runConnectorContentPass } from '@/lib/knowledge/connectors/sync-content-pass' -import { SOURCE_CONTENT_ERROR } from '@/lib/knowledge/connectors/sync-limits' +import { + SOURCE_CONTENT_ERROR, + SOURCE_PERMISSION_ERROR, +} from '@/lib/knowledge/connectors/sync-limits' import { stillHoldsSyncLock } from '@/lib/knowledge/connectors/sync-lock' import { confluenceConnector } from '@/connectors/confluence/confluence' import type { ExternalDocument, SyncResult } from '@/connectors/types' @@ -105,6 +108,7 @@ beforeEach(() => { hydrationVersion = undefined sourceBody = { value: '' } mocks.hardDelete.mockResolvedValue(0) + mocks.onPage.mockReset() mocks.upload.mockImplementation(async ({ customKey }: { customKey: string }) => ({ key: customKey, path: `/api/files/serve/${encodeURIComponent(customKey)}`, @@ -381,6 +385,45 @@ function contentWrite(): Record { } describe('content pass checkpoint intent', () => { + it('persists unresolved permissions independently of successful content processing', async () => { + sourceBody = { value: '

Current content

' } + mocks.onPage.mockResolvedValue({ permissionsIncomplete: true }) + const { pass, result } = await runPass({ access: 'admin' }) + expect(pass).toMatchObject({ + complete: true, + holdNotice: SOURCE_PERMISSION_ERROR, + checkpoint: { permissionFailures: true, contentFailures: false }, + }) + expect(result.docsFailed).toBe(0) + expect(dbChainMockFns.set).toHaveBeenCalledWith( + expect.objectContaining({ + listingCheckpoint: expect.objectContaining({ permissionFailures: true }), + }) + ) + }) + + it('does not erase an earlier worker permission failure when later pages verify successfully', async () => { + const checkpoint = { + ...beginListingCheckpoint({ + fingerprint: 'a'.repeat(64), + generationId: 'prior', + startedAt: new Date(0), + }), + permissionFailures: true, + } + mocks.onPage.mockResolvedValue({ permissionsIncomplete: false }) + const { pass } = await runPass({ checkpoint, access: 'admin' }) + expect(pass.holdNotice).toBe(SOURCE_PERMISSION_ERROR) + expect(pass.checkpoint.permissionFailures).toBe(true) + }) + + it('clears permission failure evidence for a newly verified crawl', async () => { + mocks.onPage.mockResolvedValue({ permissionsIncomplete: false }) + const { pass } = await runPass({ access: 'admin' }) + expect(pass.checkpoint.permissionFailures).toBe(false) + expect(pass.holdNotice).toBeNull() + }) + it('uses the database clock for a new generation despite a different worker clock', async () => { const databaseTime = new Date('2026-09-08T10:00:00Z') sourceBody = { value: '

Current content

' } diff --git a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts index 2031682028f..7ef42e94c5e 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts @@ -10,7 +10,10 @@ import { readListingCheckpoint, runResumableListing, } from '@/lib/knowledge/connectors/listing-checkpoint' -import { SOURCE_CONTENT_ERROR } from '@/lib/knowledge/connectors/sync-limits' +import { + SOURCE_CONTENT_ERROR, + SOURCE_PERMISSION_ERROR, +} from '@/lib/knowledge/connectors/sync-limits' import { assertSyncLeaseHeldInTx, type SyncRunLease } from '@/lib/knowledge/connectors/sync-lock' import { type KnowledgeBaseOwner, @@ -57,7 +60,10 @@ interface ContentPassInput { forceRehydrate: boolean fullSync?: boolean deadlineAt: number - onPage?: (documents: ExternalDocument[], generationStartedAt: Date) => Promise + onPage?: ( + documents: ExternalDocument[], + generationStartedAt: Date + ) => Promise<{ permissionsIncomplete: boolean } | undefined> } /** One durable content cycle shared by content-owned and member-visibility connectors. */ @@ -172,7 +178,8 @@ export async function runConnectorContentPass(input: ContentPassInput) { }, }) if (!finished) return false - await input.onPage?.(documents, startedAt) + const pageOutcome = await input.onPage?.(documents, startedAt) + if (pageOutcome?.permissionsIncomplete) cycle.permissionFailures = true await withLease(async (tx) => { const verified = externalIds.filter((id) => !state.failedExternalIds.has(id)) for (let offset = 0; offset < verified.length; offset += 500) { @@ -204,7 +211,9 @@ export async function runConnectorContentPass(input: ContentPassInput) { return { checkpoint, complete: checkpoint.complete && reconciliation.finished, - holdNotice: reconciliation.notice ?? (checkpoint.contentFailures ? SOURCE_CONTENT_ERROR : null), + holdNotice: checkpoint.permissionFailures + ? SOURCE_PERMISSION_ERROR + : (reconciliation.notice ?? (checkpoint.contentFailures ? SOURCE_CONTENT_ERROR : null)), hydratedCount, } } diff --git a/apps/sim/lib/knowledge/connectors/sync-engine.test.ts b/apps/sim/lib/knowledge/connectors/sync-engine.test.ts index dc11c0d278d..ea8dc8c500a 100644 --- a/apps/sim/lib/knowledge/connectors/sync-engine.test.ts +++ b/apps/sim/lib/knowledge/connectors/sync-engine.test.ts @@ -2182,45 +2182,54 @@ describe('completeSuccessfulSync', () => { expect(dbChainMockFns.set).not.toHaveBeenCalled() }) - it('retains an earlier worker content failure when the final page has no errors', async () => { - const { completeSuccessfulSync } = await import('@/lib/knowledge/connectors/sync-engine') - queueTableRows(schemaMock.knowledgeBase, [{ id: 'kb-1' }]) - queueTableRows(schemaMock.knowledgeConnector, [{ id: 'c-1' }]) - queueTableRows(schemaMock.document, [{ count: 4 }]) - dbChainMockFns.returning - .mockResolvedValueOnce([]) - .mockResolvedValueOnce([{ id: 'log-1' }]) - .mockResolvedValueOnce([{ id: 'c-1' }]) + it.each(['contentFailures', 'permissionFailures'] as const)( + 'retains an earlier worker %s when the final page has no errors', + async (failure) => { + const { completeSuccessfulSync } = await import('@/lib/knowledge/connectors/sync-engine') + queueTableRows(schemaMock.knowledgeBase, [{ id: 'kb-1' }]) + queueTableRows(schemaMock.knowledgeConnector, [{ id: 'c-1' }]) + queueTableRows(schemaMock.document, [{ count: 4 }]) + dbChainMockFns.returning + .mockResolvedValueOnce([]) + .mockResolvedValueOnce([{ id: 'log-1' }]) + .mockResolvedValueOnce([{ id: 'c-1' }]) - expect( - await completeSuccessfulSync( - 'c-1', - 'kb-1', - 'log-1', - 60, - { ...RESULT, docsFailed: 0 }, - 'retry', - { - complete: true, - checkpoint: { - unsafe: false, - contentFailures: true, - startedAt: '2026-09-04T00:00:00Z', - listedCount: 4, - }, - } + expect( + await completeSuccessfulSync( + 'c-1', + 'kb-1', + 'log-1', + 60, + { ...RESULT, docsFailed: 0 }, + 'retry', + { + complete: true, + checkpoint: { + unsafe: false, + [failure]: true, + startedAt: '2026-09-04T00:00:00Z', + listedCount: 4, + }, + } + ) + ).toBe(true) + expect(dbChainMockFns.set).toHaveBeenCalledWith( + expect.objectContaining({ status: 'partial', docsFailed: 0, listedCount: 4 }) ) - ).toBe(true) - expect(dbChainMockFns.set).toHaveBeenCalledWith( - expect.objectContaining({ status: 'partial', docsFailed: 0, listedCount: 4 }) - ) - const connectorUpdate = dbChainMockFns.set.mock.calls.find( - (call) => (call[0] as Record | undefined)?.status === 'active' - )?.[0] as Record - expect(connectorUpdate).not.toHaveProperty('lastSyncAt') - expect(connectorUpdate.listingCheckpoint).toBeNull() - expect((connectorUpdate.nextSyncAt as Date).getTime()).toBeGreaterThan(Date.now() + 50 * 60_000) - }) + const connectorUpdate = dbChainMockFns.set.mock.calls.find( + (call) => (call[0] as Record | undefined)?.status === 'active' + )?.[0] as Record + expect(connectorUpdate).not.toHaveProperty('lastSyncAt') + expect(connectorUpdate.lastSyncError).toBe('retry') + expect(dbChainMockFns.set).toHaveBeenCalledWith( + expect.objectContaining({ status: 'partial', errorMessage: 'retry' }) + ) + expect(connectorUpdate.listingCheckpoint).toBeNull() + expect((connectorUpdate.nextSyncAt as Date).getTime()).toBeGreaterThan( + Date.now() + 50 * 60_000 + ) + } + ) it('records a held listing as a completed sync whose watermark advances', async () => { const { completeSuccessfulSync } = await import('@/lib/knowledge/connectors/sync-engine') @@ -2283,6 +2292,64 @@ describe('completeSuccessfulSync', () => { expect.objectContaining({ status: 'active' }) ) }) + + it.each([ + { complete: true, holdNotice: null }, + { complete: false, holdNotice: null }, + { complete: false, holdNotice: 'Permissions could not be verified' }, + ])( + 'retains dispatch failures without changing listing recovery: %j', + async ({ complete, holdNotice }) => { + const { completeSuccessfulSync } = await import('@/lib/knowledge/connectors/sync-engine') + queueTableRows(schemaMock.knowledgeBase, [{ id: 'kb-1' }]) + queueTableRows(schemaMock.knowledgeConnector, [{ id: 'c-1' }]) + queueTableRows(schemaMock.document, [{ count: 4 }]) + dbChainMockFns.returning + .mockResolvedValueOnce([]) + .mockResolvedValueOnce([{ id: 'log-1' }]) + .mockResolvedValueOnce([{ id: 'c-1' }]) + + await expect( + completeSuccessfulSync( + 'c-1', + 'kb-1', + 'log-1', + 60, + { + ...RESULT, + docsFailed: 0, + processingDispatch: { requested: 1, accepted: 0, failed: 1 }, + }, + holdNotice, + { + complete, + checkpoint: { unsafe: false, startedAt: '2026-09-04T00:00:00Z', listedCount: 4 }, + } + ) + ).resolves.toBe(true) + + const notice = + holdNotice ?? + 'Some documents could not be queued for indexing. They will be retried automatically.' + expect(dbChainMockFns.set).toHaveBeenCalledWith( + expect.objectContaining({ + status: 'partial', + errorMessage: notice, + listedCount: complete ? 4 : null, + }) + ) + const update = dbChainMockFns.set.mock.calls.find(([value]) => value.status === 'active')?.[0] + expect(update).toMatchObject({ + lastSyncError: notice, + consecutiveFailures: 0, + syncLockToken: null, + }) + if (complete) expect(update.lastSyncAt).toEqual(new Date('2026-09-04T00:00:00Z')) + else expect(update).not.toHaveProperty('lastSyncAt') + if (complete) expect(update.nextSyncAt.getTime()).toBeGreaterThan(Date.now() + 50 * 60_000) + else expect(update.nextSyncAt.getTime()).toBeLessThanOrEqual(Date.now()) + } + ) }) describe('stillHoldsSyncLock', () => { @@ -2719,6 +2786,45 @@ describe('executeSync heartbeats during the listing phase', () => { } ) + it.each([ + { acl: undefined, incomplete: true }, + { acl: ['invalid-token'], incomplete: true }, + { acl: [], incomplete: false }, + { acl: ['u:reader@example.com'], incomplete: false }, + ])( + 'reports rejected mirrored permissions without rejecting valid grants: %j', + async ({ acl, incomplete }) => { + const contentPass = await import('@/lib/knowledge/connectors/sync-content-pass') + primeSyncUpToListing() + dbChainMockFns.returning.mockReset() + dbChainMockFns.returning.mockResolvedValueOnce([{ ...CONNECTOR, accessMode: 'admin' }]) + let permissionResult: { permissionsIncomplete: boolean } | undefined + const pass = vi + .spyOn(contentPass, 'runConnectorContentPass') + .mockImplementation(async (input) => { + permissionResult = await input.onPage?.( + [{ externalId: 'page-1', title: 'Page', content: 'Body', mimeType: 'text/plain', acl }], + new Date() + ) + throw new Error('Stopped after permission persistence') + }) + try { + const result = await executeSync('c-1', { + billingAttribution: { workspaceId: 'ws-1' } as never, + }) + expect(result.error).toBe('Stopped after permission persistence') + expect(permissionResult).toEqual({ permissionsIncomplete: incomplete }) + expect(dbChainMockFns.set).toHaveBeenCalledWith( + expect.objectContaining({ + acl: incomplete ? [] : acl, + }) + ) + } finally { + pass.mockRestore() + } + } + ) + it('beats between pages and abandons the run when the lock was reclaimed', async () => { const { executeSync } = await import('@/lib/knowledge/connectors/sync-engine') const { SYNC_LOCK_HEARTBEAT_INTERVAL_MS } = await import( diff --git a/apps/sim/lib/knowledge/connectors/sync-engine.ts b/apps/sim/lib/knowledge/connectors/sync-engine.ts index 78bec1bf562..dccaf32d775 100644 --- a/apps/sim/lib/knowledge/connectors/sync-engine.ts +++ b/apps/sim/lib/knowledge/connectors/sync-engine.ts @@ -122,7 +122,7 @@ async function applySourceMirroredAcls(input: { ownedExternalIds: readonly (string | null)[] lease?: SyncRunLease generationStartedAt: Date -}): Promise { +}): Promise<{ permissionsIncomplete: boolean }> { const { connectorId, connectorConfig, externalDocs } = input /** @@ -174,6 +174,7 @@ async function applySourceMirroredAcls(input: { } ) } + return { permissionsIncomplete: unattributed > 0 || written.rejected > 0 } } /** Whether an automatic connector sync may begin from this persisted state. */ @@ -296,6 +297,7 @@ export interface ContentPassOutcome { checkpoint: { unsafe: boolean contentFailures?: boolean + permissionFailures?: boolean startedAt: string listedCount: number incrementalSince?: string | null @@ -303,17 +305,18 @@ export interface ContentPassOutcome { } /** - * A content pass is incomplete when the listing has not reached the end of the - * source (the generation resumes on the next run) or a source read failed (the - * next pass replays it). `checkpoint.unsafe` is deliberately not part of this: - * it means "do not infer deletions from this listing" and is honored by the - * deletion hold in `reconcileCompletedListing`. A held pass is still a - * completed sync whose watermark advances. + * A deletion hold alone does not make a sync incomplete: `checkpoint.unsafe` + * prevents deletion reconciliation, but an otherwise successful crawl may + * still advance its watermark. */ export function isContentPassIncomplete( contentPass: Pick ): boolean { - return !contentPass.complete || contentPass.checkpoint.contentFailures === true + return ( + !contentPass.complete || + contentPass.checkpoint.contentFailures === true || + contentPass.checkpoint.permissionFailures === true + ) } /** @@ -332,6 +335,12 @@ export async function completeSuccessfulSync( reconciliationHoldNotice: string | null, contentPass?: ContentPassOutcome ): Promise { + const processingDispatchFailed = result.processingDispatch.failed > 0 + const completionNotice = + reconciliationHoldNotice ?? + (processingDispatchFailed + ? 'Some documents could not be queued for indexing. They will be retried automatically.' + : null) try { return await db.transaction(async (tx) => { const [lockedKnowledgeBase] = await tx @@ -379,7 +388,10 @@ export async function completeSuccessfulSync( const [closedLog] = await tx .update(knowledgeConnectorSyncLog) .set({ - status: contentPass && isContentPassIncomplete(contentPass) ? 'partial' : 'completed', + status: + processingDispatchFailed || (contentPass && isContentPassIncomplete(contentPass)) + ? 'partial' + : 'completed', completedAt: now, listedCount: contentPass?.complete ? contentPass.checkpoint.incrementalSince @@ -392,6 +404,7 @@ export async function completeSuccessfulSync( docsUnchanged: result.docsUnchanged, docsSkipped: result.docsSkipped, docsFailed: result.docsFailed, + errorMessage: completionNotice, }) .where( and( @@ -409,7 +422,7 @@ export async function completeSuccessfulSync( now, actualDocCount, contentPass && !contentPass.complete ? now : calculateNextSyncTime(syncIntervalMinutes), - reconciliationHoldNotice, + completionNotice, result.docsFailed === 0 && (!contentPass || !isContentPassIncomplete(contentPass)) ), /** Restored above under this same lock, or hidden by the admin pass before the ACLs it wrote. */ @@ -1110,7 +1123,7 @@ export async function executeSync( onPage: mirrored ? async (externalDocs, generationStartedAt) => { await directoryRefreshed - await applySourceMirroredAcls({ + return applySourceMirroredAcls({ connectorId, connectorConfig, sourceConfig, diff --git a/apps/sim/lib/knowledge/connectors/sync-limits.ts b/apps/sim/lib/knowledge/connectors/sync-limits.ts index 15aa207b839..0332a659513 100644 --- a/apps/sim/lib/knowledge/connectors/sync-limits.ts +++ b/apps/sim/lib/knowledge/connectors/sync-limits.ts @@ -111,6 +111,9 @@ export const MEMBER_TOMBSTONE_PURGE_DAYS = 7 /** Hard deletes one members-mode run may perform; bounds the blast radius of a bad run. */ export const MEMBER_PURGE_MAX_PER_RUN = 1000 +export const SOURCE_PERMISSION_ERROR = + 'Some document permissions could not be verified. Documents without verified access stay hidden from search.' + /** Source downloads are retried by connector listing, never by parsing the retained file again. */ export const SOURCE_CONTENT_ERROR = 'Source content could not be refreshed. The connector will retry at its next scheduled sync.' diff --git a/packages/db/migrations/0354_member_sync_failure_counts.sql b/packages/db/migrations/0354_member_sync_failure_counts.sql new file mode 100644 index 00000000000..ddad930514f --- /dev/null +++ b/packages/db/migrations/0354_member_sync_failure_counts.sql @@ -0,0 +1,2 @@ +ALTER TABLE "knowledge_connector_member_sync_log" ADD COLUMN "docs_failed" integer;--> statement-breakpoint +ALTER TABLE "knowledge_connector_member_sync_log" ADD COLUMN "processing_dispatch_failed" integer; \ No newline at end of file diff --git a/packages/db/migrations/meta/0354_snapshot.json b/packages/db/migrations/meta/0354_snapshot.json new file mode 100644 index 00000000000..60f084724f5 --- /dev/null +++ b/packages/db/migrations/meta/0354_snapshot.json @@ -0,0 +1,27242 @@ +{ + "id": "b1c466d9-d3c7-41c7-9e0c-3448f441d971", + "prevId": "40afdacd-8e2e-4cc8-8341-a06f09488b70", + "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.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 + }, + "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_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_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" + } + }, + "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.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_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_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_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_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": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "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_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_vector_hnsw_idx": { + "name": "embedding_384_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_vector_hnsw_idx": { + "name": "embedding_768_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_vector_hnsw_idx": { + "name": "embedding_1024_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_vector_hnsw_idx": { + "name": "embedding_3072_vector_hnsw_idx", + "columns": [ + { + "expression": "(\"embedding_3072\"::halfvec(3072)) halfvec_cosine_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_binary_hnsw_idx": { + "name": "embedding_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding\")::bit(1536)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_384_binary_hnsw_idx": { + "name": "embedding_384_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_384\")::bit(384)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_768_binary_hnsw_idx": { + "name": "embedding_768_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_768\")::bit(768)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_1024_binary_hnsw_idx": { + "name": "embedding_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_1024\")::bit(1024)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_3072_binary_hnsw_idx": { + "name": "embedding_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "(binary_quantize(\"embedding_3072\")::bit(3072)) bit_hamming_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "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": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_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_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 + }, + "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_binary_hnsw_idx": { + "name": "embedding_search_binary_hnsw_idx", + "columns": [ + { + "expression": "binary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_384_binary_hnsw_idx": { + "name": "embedding_search_384_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_768_binary_hnsw_idx": { + "name": "embedding_search_768_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_1024_binary_hnsw_idx": { + "name": "embedding_search_1024_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_3072_binary_hnsw_idx": { + "name": "embedding_search_3072_binary_hnsw_idx", + "columns": [ + { + "expression": "binary_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "bit_hamming_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "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", + "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 + }, + "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", + "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 + } + }, + "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 + }, + "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_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 + } + }, + "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')" + } + }, + "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 + } + }, + "indexes": { + "kcsl_connector_started_at_idx": { + "name": "kcsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcsl_started_at_partial_idx": { + "name": "kcsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector_sync_log\".\"status\" = 'started'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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.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 + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_secret_provenance": { + "name": "memory_secret_provenance", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memory_secret_provenance_memory_id_memory_id_fk": { + "name": "memory_secret_provenance_memory_id_memory_id_fk", + "tableFrom": "memory_secret_provenance", + "tableTo": "memory", + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_secret_provenance_status_check": { + "name": "memory_secret_provenance_status_check", + "value": "\"memory_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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_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.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.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_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_period_cost_idx": { + "name": "usage_log_billing_period_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_created_at_cost_idx": { + "name": "usage_log_billing_entity_created_at_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_deadline_idx": { + "name": "workflow_execution_logs_running_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_started_at_idx": { + "name": "workflow_execution_logs_redacting_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'redacting'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_deadline_idx": { + "name": "workflow_execution_logs_redacting_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "mounted_secrets": { + "name": "mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_operation", + "columnsFrom": ["deployment_operation_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_secret_scope": { + "name": "inbox_secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "inbox_mounted_secrets": { + "name": "inbox_mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_inbox_provider_id_idx": { + "name": "workspace_inbox_provider_id_idx", + "columns": [ + { + "expression": "inbox_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_storage_used_bytes_non_negative": { + "name": "workspace_storage_used_bytes_non_negative", + "value": "\"workspace\".\"storage_used_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_collab_state": { + "name": "workspace_file_collab_state", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "doc_state": { + "name": "doc_state", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_collab_state_file_id_workspace_files_id_fk": { + "name": "workspace_file_collab_state_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_collab_state", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_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_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_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_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": "now()" + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_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 + } + }, + "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_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 3fd48951b0e..ab969178aa4 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2472,6 +2472,13 @@ "when": 1789591219081, "tag": "0353_search_mcp_activity", "breakpoints": true + }, + { + "idx": 354, + "version": "7", + "when": 1789600824945, + "tag": "0354_member_sync_failure_counts", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 335fbd9dc94..1c8fabba9fd 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -6009,6 +6009,9 @@ export const knowledgeConnectorMemberSyncLog = pgTable( membersCompleted: integer('members_completed').notNull().default(0), membersIncomplete: integer('members_incomplete').notNull().default(0), membersFailed: integer('members_failed').notNull().default(0), + /** Null on historical runs that did not record document failure counts. */ + docsFailed: integer('docs_failed'), + processingDispatchFailed: integer('processing_dispatch_failed'), docsListed: integer('docs_listed').notNull().default(0), docsAdded: integer('docs_added').notNull().default(0), docsUpdated: integer('docs_updated').notNull().default(0), diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts index e44f7a4b5ab..625dbab0d29 100644 --- a/packages/testing/src/mocks/schema.mock.ts +++ b/packages/testing/src/mocks/schema.mock.ts @@ -1777,6 +1777,8 @@ export const schemaMock = { membersCompleted: 'knowledgeConnectorMemberSyncLog.membersCompleted', membersIncomplete: 'knowledgeConnectorMemberSyncLog.membersIncomplete', membersFailed: 'knowledgeConnectorMemberSyncLog.membersFailed', + docsFailed: 'knowledgeConnectorMemberSyncLog.docsFailed', + processingDispatchFailed: 'knowledgeConnectorMemberSyncLog.processingDispatchFailed', docsListed: 'knowledgeConnectorMemberSyncLog.docsListed', docsAdded: 'knowledgeConnectorMemberSyncLog.docsAdded', docsUpdated: 'knowledgeConnectorMemberSyncLog.docsUpdated', From ff25be7b1f1e5a667905ceb23cd6ec9fec3baad2 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 16 Sep 2026 17:05:49 -0700 Subject: [PATCH 30/43] fix(search): preserve indexed vector retrieval and literal source text (#7899) * fix(search): preserve indexed vector retrieval and literal source text * fix(search): keep benchmark corpus defaults within valid bounds * fix(search): retain ANN recall settings and verify distinct queries --- apps/sim/lib/file-parsers/index.ts | 2 +- apps/sim/lib/file-parsers/sniff.test.ts | 32 ++ apps/sim/lib/file-parsers/sniff.ts | 14 +- apps/sim/lib/file-parsers/types.ts | 2 + .../search-latency.integration.ts | 349 ++++++++++++++---- .../knowledge/documents/document-processor.ts | 5 +- .../stored-artifact-extension.test.ts | 60 ++- apps/sim/lib/knowledge/search/queries.test.ts | 6 +- apps/sim/lib/knowledge/search/queries.ts | 42 ++- 9 files changed, 414 insertions(+), 98 deletions(-) diff --git a/apps/sim/lib/file-parsers/index.ts b/apps/sim/lib/file-parsers/index.ts index 7c4cc43a512..03bf72c26b8 100644 --- a/apps/sim/lib/file-parsers/index.ts +++ b/apps/sim/lib/file-parsers/index.ts @@ -176,7 +176,7 @@ export async function parseBuffer( } const kind = sniffFileKind(buffer, normalizedExtension) - const route = reconcileParserRoute(normalizedExtension, kind) + const route = reconcileParserRoute(normalizedExtension, kind, options) const parser = PARSERS.get(route.extension) if (!parser?.parseBuffer) { diff --git a/apps/sim/lib/file-parsers/sniff.test.ts b/apps/sim/lib/file-parsers/sniff.test.ts index 5e94bc89698..e79f0a575c0 100644 --- a/apps/sim/lib/file-parsers/sniff.test.ts +++ b/apps/sim/lib/file-parsers/sniff.test.ts @@ -308,6 +308,38 @@ describe('parseBuffer reconciles the extension with the sniffed bytes', () => { expect(result.metadata?.detectedType).toBe('html') }) + it.each([ + '
', + '{\\rtf1\\ansi Source-format example}', + ])( + 'preserves textual markup when the caller supplies a canonical text artifact', + async (content) => { + const result = await parseBuffer(Buffer.from(content), 'txt', { textMode: 'literal' }) + + expect(result.content).toBe(content) + expect(result.metadata?.detectedType).toBeUndefined() + } + ) + + it('keeps literal-text handling scoped to txt artifacts', async () => { + const result = await parseBuffer( + Buffer.from('

Readable page

'), + 'html', + { textMode: 'literal' } + ) + + expect(result.content).toContain('Readable page') + expect(result.content).not.toContain('') + await expect( + parseBuffer(Buffer.from('403 Forbidden'), 'json', { + textMode: 'literal', + }) + ).rejects.toMatchObject({ code: 'invalid_format' }) + await expect(parseBuffer(oleBinary(), 'txt', { textMode: 'literal' })).rejects.toMatchObject({ + code: 'invalid_format', + }) + }) + it('extracts a docx labelled .xlsx through the Word parser', async () => { const result = await parseBuffer(await buildDocx('Office Relocation'), 'xlsx') diff --git a/apps/sim/lib/file-parsers/sniff.ts b/apps/sim/lib/file-parsers/sniff.ts index 365b4c4b66f..a976b97e22b 100644 --- a/apps/sim/lib/file-parsers/sniff.ts +++ b/apps/sim/lib/file-parsers/sniff.ts @@ -1,5 +1,6 @@ import { FileParserError } from '@/lib/file-parsers/errors' import { isEncryptedOoxmlContainer } from '@/lib/file-parsers/ooxml-encryption' +import type { FileParseOptions } from '@/lib/file-parsers/types' import { decodeTextBuffer, detectBomlessUtf16 } from '@/lib/file-parsers/utils' import { isZipShaped } from '@/lib/file-parsers/zip-guard' @@ -307,7 +308,18 @@ function invalidFormat(extension: string, kind: SniffedKind): FileParserError { * text (as CSV under a spreadsheet extension), and an OLE2 file under a modern * Word extension is the legacy `.doc` parser's job. Legacy `.ppt` has no reader. */ -export function reconcileParserRoute(extension: string, kind: SniffedKind): ParserRoute { +export function reconcileParserRoute( + extension: string, + kind: SniffedKind, + options: Pick = {} +): ParserRoute { + if ( + extension === 'txt' && + options.textMode === 'literal' && + (kind === 'html' || kind === 'rtf') + ) { + return { extension } + } if (kind === 'rtf') { throw new FileParserError( 'unsupported_type', diff --git a/apps/sim/lib/file-parsers/types.ts b/apps/sim/lib/file-parsers/types.ts index 36b059a3bc7..028e2e7382b 100644 --- a/apps/sim/lib/file-parsers/types.ts +++ b/apps/sim/lib/file-parsers/types.ts @@ -33,6 +33,8 @@ export interface FileParseResult { export interface FileParseOptions { signal?: AbortSignal + /** Preserve textual markup in a canonical .txt artifact instead of interpreting it as HTML or RTF. */ + textMode?: 'literal' /** Complete PDF extraction rejects safety limits instead of returning preview text. */ pdfTextMode?: 'preview' | 'complete' } diff --git a/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts b/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts index 899982f5b78..0c89994ee4f 100644 --- a/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts @@ -54,7 +54,6 @@ vi.hoisted(() => { if (process.env.KNOWLEDGE_SEARCH_PERFORMANCE_TEST === 'true') { Object.assign(process.env, { OPENAI_API_KEY: 'isolated-embedding-http-fixture', - GEMINI_API_KEY: 'isolated-gemini-http-fixture', CONFLUENCE_CLIENT_ID: 'isolated-confluence-fixture-client', CONFLUENCE_CLIENT_SECRET: 'isolated-confluence-fixture-secret', }) @@ -63,10 +62,17 @@ vi.hoisted(() => { const externalFetch = globalThis.fetch const enabled = process.env.KNOWLEDGE_SEARCH_PERFORMANCE_TEST === 'true' +const batchSize = 1000 +const MIN_CHUNK_COUNT = 5000 const chunkCount = Number(process.env.KNOWLEDGE_SEARCH_PERFORMANCE_CHUNKS ?? 20_000) +const unrelatedChunkCount = Number( + process.env.KNOWLEDGE_SEARCH_PERFORMANCE_UNRELATED_CHUNKS ?? + Math.max(MIN_CHUNK_COUNT, Math.ceil(chunkCount / (2 * batchSize)) * batchSize) +) +const evictSharedBuffers = process.env.KNOWLEDGE_SEARCH_PERFORMANCE_EVICT_BUFFERS === 'true' const dimensions = 1536 +const candidateDimensions = 512 const chunksPerDocument = 4 -const batchSize = 1000 const logger = createLogger('SearchLatencyIntegration') const fixtureSchema = z.object({ aliceId: z.uuid(), @@ -84,7 +90,11 @@ function readFixtureReport(file: string) { /** Captured SQL plans include repeated high-dimensional query parameters. */ if (statSync(file).size > 64 * 1024 * 1024) throw new Error('Fixture report exceeds 64 MiB') return z - .object({ fixture: fixtureSchema, unrelatedFixture: fixtureSchema }) + .object({ + fixture: fixtureSchema, + unrelatedFixture: fixtureSchema, + method: z.object({ fixtureVersion: z.literal(2) }), + }) .parse(JSON.parse(readFileSync(file, 'utf8'))) } const reused = reuseFile ? readFixtureReport(reuseFile) : undefined @@ -93,7 +103,11 @@ const unrelated = reused?.unrelatedFixture ?? createKnowledgeAclFixtureIds() const organizationChatId = generateId() function topicVector(topic = 0) { const vector = Array.from({ length: dimensions }, (_, index) => - Math.sin((index + 1) * (topic + 1) * 12.9898) + Math.sin( + (((index * 137 + Math.floor(index / candidateDimensions) * 57) % candidateDimensions) + 1) * + (topic + 1) * + 12.9898 + ) ) const magnitude = Math.hypot(...vector) return vector.map((value) => value / magnitude) @@ -104,15 +118,23 @@ const report: Record = { fixture: ids, unrelatedFixture: unrelated, method: { + fixtureVersion: 2, chunkCount, + unrelatedChunkCount, dimensions, + candidateDimensions, chunksPerDocument, sql: 'Captured from the real Assistant tool; no hand-written search query', providers: 'Embedding and source-permission HTTP responses are controlled; internal search and authorization code is real', vectors: - 'Normalized topic clusters with deterministic dense noise; not semantic-quality evaluation', - cache: 'First and repeated samples; no claim of a cold operating-system cache', + 'Normalized 512-dimensional topic/noise geometry with permuted copies across 1536 dimensions; verifies prefix candidate ranking, not semantic embedding quality', + cache: evictSharedBuffers + ? 'Organization samples evict PostgreSQL shared buffers before each request; operating-system cache is not cleared' + : 'First and repeated samples; no claim of a cold operating-system cache', + layout: reused + ? 'Reused fixture; physical layout is inherited from its original report' + : 'Tenant batches interleaved; chunks permuted across document identities', }, } let capture = false @@ -131,8 +153,13 @@ interface ExplainNode { 'Node Type': string 'Actual Rows': number 'Actual Loops': number + 'Plan Rows'?: number + 'Shared Hit Blocks'?: number + 'Shared Read Blocks'?: number 'Index Name'?: string 'Relation Name'?: string + 'Subplan Name'?: string + 'CTE Name'?: string Output?: string[] Plans?: ExplainNode[] } @@ -143,8 +170,13 @@ const explainNodeSchema: z.ZodType = z.lazy(() => 'Node Type': z.string(), 'Actual Rows': z.number(), 'Actual Loops': z.number(), + 'Plan Rows': z.number().optional(), + 'Shared Hit Blocks': z.number().optional(), + 'Shared Read Blocks': z.number().optional(), 'Index Name': z.string().optional(), 'Relation Name': z.string().optional(), + 'Subplan Name': z.string().optional(), + 'CTE Name': z.string().optional(), Output: z.array(z.string()).optional(), Plans: z.array(explainNodeSchema).optional(), }) @@ -160,6 +192,43 @@ function assertCompactCandidates(node: ExplainNode) { for (const child of node.Plans ?? []) assertCompactCandidates(child) } +function explainNodes(node: ExplainNode): ExplainNode[] { + return [node, ...(node.Plans ?? []).flatMap(explainNodes)] +} + +/** Broad ranking must stop the ordered ANN scan instead of sorting every accessible chunk. */ +function assertIndexedCandidates(plan: ExplainNode, candidateLimit: number) { + const nodes = explainNodes(plan) + const initial = nodes.find((node) => node['Subplan Name'] === 'CTE initial_candidates') + expect(initial).toBeDefined() + const candidateNodes = explainNodes(initial!) + expect( + candidateNodes.some( + (node) => + node['Index Name'] === 'embedding_search_512_cosine_hnsw_idx' && node['Actual Loops'] > 0 + ) + ).toBe(true) + expect(candidateNodes.some((node) => node['Node Type'] === 'Sort')).toBe(false) + expect( + candidateNodes.some((node) => node['Index Name'] === 'embedding_search_document_lookup_idx') + ).toBe(false) + const filtered = nodes.find((node) => node['Subplan Name'] === 'CTE filtered_scores') + expect(filtered).toBeDefined() + expect(filtered!.Output).toHaveLength(3) + expect(filtered!.Output![2]).toContain('<=>') + if (initial!['Actual Rows'] >= candidateLimit) { + for (const node of nodes.filter( + (item) => + item['Subplan Name'] === 'CTE visible_search_documents' || + item['CTE Name'] === 'visible_search_documents' || + item['Subplan Name'] === 'CTE filtered_scores' || + item['CTE Name'] === 'filtered_scores' + )) { + expect(node['Actual Loops']).toBe(0) + } + } +} + /** Small scopes must seek chunk metadata by document without reading the full vector projection. */ function assertIndexedChunkProbe(node: ExplainNode): number { let lookups = 0 @@ -186,12 +255,30 @@ function saveReport() { if (file) writeFileSync(file, JSON.stringify(report, null, 2), { mode: 0o600 }) } +/** Only the disposable fixture may evict shared buffers; the operating-system cache stays intact. */ +async function prepareOrganizationSample(label: string) { + if (!evictSharedBuffers) return + const [eviction] = await db.execute<{ buffers: number; evicted: number }>(sql` + WITH cached AS MATERIALIZED ( + SELECT bufferid FROM pg_buffercache + WHERE reldatabase = (SELECT oid FROM pg_database WHERE datname = current_database()) + ) SELECT count(*)::int AS buffers, + count(*) FILTER (WHERE pg_buffercache_evict(bufferid))::int AS evicted + FROM cached + `) + report[`${label}.sharedBufferEviction`] = eviction + saveReport() +} + const diagnosticSchema = z .object({ surface: z.enum(['dashboard', 'copilot']), outcome: z.enum(['success', 'partial']), elapsedMs: z.number(), vectorBudgetMs: z.number().positive(), + vectorCandidateDimensions: z.number().optional(), + vectorCandidateLimit: z.number().optional(), + vectorCandidateScan: z.enum(['planned', 'filtered']).optional(), retrievalStatus: z.enum(['complete', 'partial']), timedOutLegs: z.array(z.enum(['vector', 'keyword', 'tags'])), toolResultBytes: z.number().int().nonnegative().optional(), @@ -223,11 +310,12 @@ async function search( userId = ids.aliceId, query = 'Orion deployment', filters: WorkspaceSearchFilters = {}, - organizationScope = false + organizationScope = false, + topK = 15 ) { return resultSchema.parse( await searchWorkspaceServerTool.execute( - { query, topK: 15, ...filters }, + { query, topK, ...filters }, { userId, ...(organizationScope @@ -245,7 +333,12 @@ async function search( ) } -async function searchDashboard(query = 'Orion deployment', userId = ids.aliceId) { +async function searchDashboard( + query = 'Orion deployment', + userId = ids.aliceId, + organizationScope = false, + topK = 15 +) { const authenticate = vi.spyOn(internalSessionAuth, 'authenticate').mockResolvedValue({ kind: 'session', userId, @@ -257,9 +350,11 @@ async function searchDashboard(query = 'Orion deployment', userId = ids.aliceId) method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ - workspaceId: ids.workspaceId, + ...(organizationScope + ? { organizationId: ids.organizationId } + : { workspaceId: ids.workspaceId }), query, - topK: 15, + topK, }), }) ) @@ -285,7 +380,11 @@ function expectCompleteVectorSearch(diagnostics: z.infer ReturnType) { +async function sample( + label: string, + run: () => ReturnType, + options: { explain?: boolean } = {} +) { captured.length = 0 diagnosticLog?.mockClear() const start = performance.now() @@ -328,8 +427,22 @@ async function sample(label: string, run: () => ReturnType) { item.query.includes('WITH scored_search_candidates') || item.query.includes('WITH visible_keyword_documents')) ) - const plans = [] - for (const query of searches) { + const plans: Array< + CapturedQuery & { + kind: 'keyword' | 'vector' | 'rerank' | 'probe' + plan: z.infer + } + > = [] + report[label] = { + milliseconds, + diagnostics, + queryCount: captured.length, + resultCount: result.data.results.length, + explainsDeferred: options.explain === false, + plans, + } + saveReport() + for (const query of options.explain === false ? [] : searches) { const plan = await db.$client.begin(async (tx) => { await tx.unsafe("SET LOCAL statement_timeout = '45s'") await tx.unsafe('SET LOCAL jit = off') @@ -349,9 +462,6 @@ async function sample(label: string, run: () => ReturnType) { ) }) const parsedPlan = explainSchema.parse(plan[0]['QUERY PLAN']) - if (query.query.includes('WITH visible_keyword_documents')) { - assertScalarKeywordSorts(parsedPlan[0].Plan) - } plans.push({ kind: query.query.includes('keyword_rank') ? 'keyword' @@ -366,15 +476,17 @@ async function sample(label: string, run: () => ReturnType) { parameters: query.parameters, plan: parsedPlan, }) + saveReport() + if (query.query.includes('WITH visible_search_documents')) { + expect(query.query).toContain('"embedding_search"."vector_512"') + expect(diagnostics.vectorCandidateDimensions).toBe(candidateDimensions) + expect(diagnostics.vectorCandidateLimit).toBeGreaterThan(0) + assertIndexedCandidates(parsedPlan[0].Plan, diagnostics.vectorCandidateLimit!) + } + if (query.query.includes('WITH visible_keyword_documents')) { + assertScalarKeywordSorts(parsedPlan[0].Plan) + } } - report[label] = { - milliseconds, - diagnostics, - queryCount: captured.length, - resultCount: result.data.results.length, - plans, - } - saveReport() logger.info(label, { milliseconds, queryCount: captured.length, @@ -386,13 +498,16 @@ async function sample(label: string, run: () => ReturnType) { describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpus', () => { beforeAll(async () => { if ( - !Number.isInteger(chunkCount) || - chunkCount < 10_000 || - chunkCount > 200_000 || - chunkCount % batchSize !== 0 + [chunkCount, unrelatedChunkCount].some( + (count) => + !Number.isInteger(count) || + count < MIN_CHUNK_COUNT || + count > 200_000 || + count % batchSize !== 0 + ) ) throw new Error( - 'KNOWLEDGE_SEARCH_PERFORMANCE_CHUNKS must be a multiple of 1000 from 10000 to 200000' + 'Search performance chunk counts must be multiples of 1000 from 5000 to 200000' ) vi.stubGlobal('fetch', async (input: string | URL | Request, init?: RequestInit) => { const url = input instanceof Request ? input.url : String(input) @@ -405,33 +520,14 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu ? new Response(null, { status: 403 }) : Response.json({ type: 'known', accountId: ids.aliceId }) } - if ( - url === - 'https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:batchEmbedContents' - ) { - const body = z - .object({ - requests: z - .array( - z.object({ - content: z.object({ parts: z.array(z.object({ text: z.string() })).length(1) }), - }) - ) - .length(1), - }) - .parse(JSON.parse(String(init?.body))) - embeddingCalls++ - const text = body.requests[0].content.parts[0].text - const topic = Number(/^Topic (\d+) deployment$/.exec(text)?.[1] ?? 0) - return Response.json({ - embeddings: [{ values: topicVector(topic) }], - usageMetadata: { promptTokenCount: 4 }, - }) - } if (url !== 'https://api.openai.com/v1/embeddings') throw new Error(`Unexpected outbound request in search fixture: ${new URL(url).origin}`) const body = z - .object({ input: z.array(z.string()).length(1), encoding_format: z.literal('base64') }) + .object({ + input: z.array(z.string()).length(1), + encoding_format: z.literal('base64'), + model: z.literal('text-embedding-3-small'), + }) .parse(JSON.parse(String(init?.body))) embeddingCalls += body.input.length const bytes = Buffer.alloc(dimensions * 4) @@ -457,14 +553,14 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu const [size] = await db.execute<{ count: number }>( sql`SELECT count(*)::int AS count FROM embedding WHERE knowledge_base_id = ${fixture.knowledgeBaseId}` ) - expect(size.count).toBe(fixture === ids ? chunkCount : chunkCount / 2) + expect(size.count).toBe(fixture === ids ? chunkCount : unrelatedChunkCount) } await db .update(knowledgeBase) .set({ workspaceId: ids.workspaceId, organizationId: null, - embeddingModel: 'gemini-embedding-001', + embeddingModel: 'text-embedding-3-small', }) .where(eq(knowledgeBase.id, ids.knowledgeBaseId)) await db @@ -490,10 +586,10 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu } else { await seedKnowledgeAclFixture(ids, { connectorType: 'google_drive' }) await seedKnowledgeAclFixture(unrelated, { connectorType: 'google_drive' }) - /** These arbitrary dense vectors are not trained for prefix shortening. */ + /** The controlled geometry preserves prefix distances for the production 512-dimensional path. */ await db .update(knowledgeBase) - .set({ embeddingModel: 'gemini-embedding-001' }) + .set({ embeddingModel: 'text-embedding-3-small' }) .where(inArray(knowledgeBase.id, [ids.knowledgeBaseId, unrelated.knowledgeBaseId])) await db .update(knowledgeBase) @@ -507,7 +603,7 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu for (const index of indexes) await db.execute(sql`DROP INDEX ${sql.identifier(index.indexname)}`) for (const fixture of [ids, unrelated]) { - const count = fixture === ids ? chunkCount : chunkCount / 2 + const count = fixture === ids ? chunkCount : unrelatedChunkCount for (let first = 0; first < count / chunksPerDocument; first += batchSize) { const last = Math.min(first + batchSize, count / chunksPerDocument) - 1 await db.execute(sql`INSERT INTO document @@ -517,7 +613,11 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu ARRAY[${`u:${fixture.aliceId}@fixture.test`}]::text[], statement_timestamp() FROM generate_series(${first}::int, ${last}::int) n`) } - for (let first = 0; first < count; first += batchSize) { + } + for (let first = 0; first < Math.max(chunkCount, unrelatedChunkCount); first += batchSize) { + for (const fixture of [ids, unrelated]) { + const count = fixture === ids ? chunkCount : unrelatedChunkCount + if (first >= count) continue const last = Math.min(first + batchSize, count) - 1 await db.transaction(async (tx) => { await tx.execute(sql`SET LOCAL jit = off`) @@ -530,22 +630,37 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu 3000, 750, 0, 3000, l2_normalize(ARRAY(SELECT (sin(coordinate * (n % 32 + 1) * 12.9898) + 0.25 * sin(n::double precision * coordinate * 12.9898 + coordinate * 78.233))::real - FROM generate_series(1, ${dimensions}) coordinate)::vector(1536)) - FROM generate_series(${first}::int, ${last}::int) n`) + FROM ( + SELECT (((position - 1) * 137 + ((position - 1) / ${candidateDimensions}) * 57) + % ${candidateDimensions}) + 1 AS coordinate + FROM generate_series(1, ${dimensions}) position + ) coordinates)::vector(1536)) + FROM ( + SELECT (ordinal * 7919) % ${count} AS n + FROM generate_series(${first}::int, ${last}::int) ordinal + ) shuffled`) }) } - logger.info('Synthetic corpus loaded', { chunks: count }) } + logger.info('Synthetic corpora loaded', { chunkCount, unrelatedChunkCount }) for (const index of indexes) await db.execute(sql.raw(index.indexdef)) } await db.execute(sql`ANALYZE document`) await db.execute(sql`ANALYZE embedding`) await db.execute(sql`ANALYZE embedding_search`) await db.execute(sql`ANALYZE embedding_keyword_search`) + if (evictSharedBuffers) await db.execute(sql`CREATE EXTENSION IF NOT EXISTS pg_buffercache`) report.server = ( await db.execute(sql`SELECT version(), current_setting('work_mem') AS work_mem, (SELECT extversion FROM pg_extension WHERE extname = 'vector') AS pgvector`) )[0] + report.relations = await db.execute(sql` + SELECT relname, pg_relation_size(oid) AS bytes + FROM pg_class + WHERE relname IN ('embedding', 'embedding_search', 'document') + OR relname LIKE 'embedding_search%hnsw_idx' + ORDER BY relname + `) db.$client.options.debug = (_connection, query, parameters) => { if (capture && captured.length < 300) captured.push({ query, parameters: [...parameters] }) } @@ -900,7 +1015,7 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu } }, 180_000) - it.each([200, 396, 400])( + it.each([200, 396, 400, 1000, 2000])( 'keeps a selective scope of %s chunks within both retrieval budgets', async (count) => { const documentCount = count / chunksPerDocument @@ -927,9 +1042,30 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu true ) const probe = plans.find((plan) => plan.kind === 'probe')! - expect(probe.plan[0].Plan['Actual Rows']).toBe(count) - expect(assertIndexedChunkProbe(probe.plan[0].Plan)).toBe(documentCount) + expect(probe.plan[0].Plan['Actual Rows']).toBe(Math.min(count, 400)) + expect(assertIndexedChunkProbe(probe.plan[0].Plan)).toBe(Math.min(documentCount, 100)) expect(plans.filter((plan) => plan.kind === 'vector')).toHaveLength(count < 400 ? 0 : 1) + if (count > 400) { + const rerank = plans.find((plan) => plan.kind === 'rerank')! + const actual = await db.$client.unsafe(rerank.query, rerank.parameters).values() + const expected = await db.execute<{ id: string }>(sql`SELECT id FROM embedding + WHERE knowledge_base_id = ${ids.knowledgeBaseId} AND enabled + AND document_id IN (${sql.join( + documentIds.map((id) => sql`${id}`), + sql`, ` + )}) + ORDER BY (embedding <=> ${JSON.stringify(queryVector)}::vector) + 0, id + LIMIT ${actual.length}`) + const expectedIds = new Set(expected.map(({ id }) => id)) + const recall = actual.filter(([id]) => expectedIds.has(id)).length / expected.length + expect(recall).toBeGreaterThanOrEqual(0.95) + report[`recall.selective-${count}.${surface}`] = { + neighbors: expected.length, + recall, + candidateScan: diagnostics.vectorCandidateScan, + } + saveReport() + } } } finally { await db @@ -1050,7 +1186,7 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu it('runs two independent Assistant searches concurrently', async () => { diagnosticLog?.mockClear() const start = performance.now() - const results = await Promise.all([search(), search(ids.aliceId, 'Engineering operations')]) + const results = await Promise.all([search(), search(ids.aliceId, 'Topic 11 deployment')]) const completed = diagnosticLog!.mock.calls .filter(([message]) => message === 'Knowledge search completed') .map(([, metadata]) => diagnosticSchema.parse(metadata)) @@ -1083,7 +1219,7 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu expect(restored.result.data.results).toHaveLength(15) }, 180_000) - it('uses the same indexed retrieval through a persisted private organization Assistant chat', async () => { + it('keeps organization searches complete with stale ACL estimates and concurrent requests', async () => { await db.insert(member).values({ id: generateId(), organizationId: ids.organizationId, @@ -1104,17 +1240,72 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu .update(knowledgeConnector) .set({ connectorType: 'google_drive', credentialId: null, sourceConfig: {} }) .where(eq(knowledgeConnector.id, ids.connectorId)) - await db.execute( - sql`UPDATE document SET acl = ARRAY[${`u:${ids.aliceId}@fixture.test`}] WHERE knowledge_base_id = ${ids.knowledgeBaseId}` - ) - await db.execute(sql`ANALYZE document`) - const { result } = await sample('organization', () => - search(ids.aliceId, 'Orion deployment', {}, true) - ) - expect(result.data.results).toHaveLength(15) - expect(result.data.results.every((row) => row.knowledgeBaseId === ids.knowledgeBaseId)).toBe( - true - ) + /** Keep the deliberate tenfold visibility underestimate until the measured requests finish. */ + await db.execute(sql`ALTER TABLE document SET (autovacuum_enabled = false)`) + try { + await db.execute(sql`UPDATE document + SET acl = ARRAY[CASE WHEN external_id::int % 10 = 0 + THEN ${`u:${ids.aliceId}@fixture.test`} ELSE ${`u:${ids.bobId}@fixture.test`} END] + WHERE knowledge_base_id = ${ids.knowledgeBaseId}`) + await db.execute(sql`ANALYZE document`) + await db.execute(sql`UPDATE document SET acl = ARRAY[${`u:${ids.aliceId}@fixture.test`}] + WHERE knowledge_base_id = ${ids.knowledgeBaseId}`) + report.organizationVisibility = { + analyzedVisibleDocuments: chunkCount / chunksPerDocument / 10, + actualVisibleDocuments: chunkCount / chunksPerDocument, + unrelatedChunks: unrelatedChunkCount, + } + /** Capture latency samples before EXPLAIN ANALYZE can warm the candidate paths. */ + for (const surface of ['dashboard', 'copilot'] as const) { + const label = `organization.${surface}` + await prepareOrganizationSample(label) + const { result, diagnostics } = await sample( + label, + () => + surface === 'dashboard' + ? searchDashboard('Orion deployment', ids.aliceId, true, 20) + : search(ids.aliceId, 'Orion deployment', {}, true, 20), + { explain: false } + ) + expectCompleteVectorSearch(diagnostics) + expect(result.data.results).toHaveLength(20) + expect( + result.data.results.every((row) => row.knowledgeBaseId === ids.knowledgeBaseId) + ).toBe(true) + } + await prepareOrganizationSample('organization.concurrent') + diagnosticLog?.mockClear() + const started = performance.now() + const results = await Promise.all([ + search(ids.aliceId, 'Orion deployment', {}, true, 20), + search(ids.aliceId, 'Topic 11 deployment', {}, true, 20), + ]) + const diagnostics = diagnosticLog!.mock.calls + .filter(([message]) => message === 'Knowledge search completed') + .map(([, metadata]) => diagnosticSchema.parse(metadata)) + report['organization.concurrent'] = { + milliseconds: performance.now() - started, + resultCounts: results.map((result) => result.data.results.length), + diagnostics, + } + saveReport() + expect(diagnostics).toHaveLength(2) + for (const item of diagnostics) expectCompleteVectorSearch(item) + for (const result of results) { + expect(result.data.results).toHaveLength(20) + expect( + result.data.results.every((row) => row.knowledgeBaseId === ids.knowledgeBaseId) + ).toBe(true) + } + const planned = await sample('organization.plans', () => + search(ids.aliceId, 'Orion deployment', {}, true, 20) + ) + expectCompleteVectorSearch(planned.diagnostics) + expect(planned.plans.filter((plan) => plan.kind === 'vector')).toHaveLength(1) + } finally { + await db.execute(sql`ALTER TABLE document RESET (autovacuum_enabled)`) + await db.execute(sql`ANALYZE document`) + } }, 180_000) /** Opt in with local Sim and Go URLs; uses the real configured provider, billing adapter, and async resume protocol. */ it.skipIf(!process.env.KNOWLEDGE_SEARCH_ASSISTANT_URL)( diff --git a/apps/sim/lib/knowledge/documents/document-processor.ts b/apps/sim/lib/knowledge/documents/document-processor.ts index a0691b43220..b285dd68d09 100644 --- a/apps/sim/lib/knowledge/documents/document-processor.ts +++ b/apps/sim/lib/knowledge/documents/document-processor.ts @@ -1350,10 +1350,11 @@ async function parseHttpFile( access.signal?.throwIfAborted() /** Prefer what we actually downloaded over what the document is *called*. */ - const extension = - resolveStoredArtifactExtension(fileUrl) ?? resolveParserExtension(filename, mimeType) + const storedExtension = resolveStoredArtifactExtension(fileUrl) + const extension = storedExtension ?? resolveParserExtension(filename, mimeType) const result = await parseBuffer(buffer, extension, { signal: access.signal, + textMode: storedExtension === 'txt' ? 'literal' : undefined, pdfTextMode: extension === 'pdf' ? 'complete' : undefined, }) return result diff --git a/apps/sim/lib/knowledge/documents/stored-artifact-extension.test.ts b/apps/sim/lib/knowledge/documents/stored-artifact-extension.test.ts index b30446cd547..18267da5b2a 100644 --- a/apps/sim/lib/knowledge/documents/stored-artifact-extension.test.ts +++ b/apps/sim/lib/knowledge/documents/stored-artifact-extension.test.ts @@ -9,7 +9,13 @@ * SharePoint PDFs with `Invalid PDF structure.` and silently double-wrapped * every spreadsheet, which "succeeded" because SheetJS accepts almost anything. */ -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' + +const { mockDownload } = vi.hoisted(() => ({ mockDownload: vi.fn() })) + +vi.mock('@/lib/uploads/utils/file-utils.server', () => ({ downloadFileFromUrl: mockDownload })) + +import { processDocument } from '@/lib/knowledge/documents/document-processor' import { resolveStoredArtifactExtension } from '@/lib/knowledge/documents/parser-extension' const CONNECTOR_PDF_URL = @@ -86,3 +92,55 @@ describe('resolveStoredArtifactExtension', () => { expect(resolveStoredArtifactExtension('/api/files/serve/s3/kb%2F1-a-Report.PDF')).toBe('pdf') }) }) + +describe('stored text document processing', () => { + const source = ` + + + + Application shell + + + +
+` + + it('indexes the source of an HTML shell stored as connector text', async () => { + mockDownload.mockResolvedValue(Buffer.from(source)) + + const result = await processDocument( + '/api/files/serve/s3/kb%2Ffixture-index.html.txt?context=knowledge-base', + 'index.html', + 'text/plain' + ) + + expect(result.chunks).toHaveLength(1) + expect(result.chunks[0].text).toContain('') + expect(result.chunks[0].text).toContain('
') + expect(result.metadata.characterCount).toBe(source.length) + }) + + it('keeps an actual HTML document on rendered-text extraction', async () => { + mockDownload.mockResolvedValue(Buffer.from(source)) + + await expect( + processDocument( + '/api/files/serve/s3/kb%2Ffixture-page.html?context=knowledge-base', + 'page.html', + 'text/html' + ) + ).rejects.toMatchObject({ code: 'no_extractable_text' }) + }) + + it('still rejects a stored text artifact containing only whitespace', async () => { + mockDownload.mockResolvedValue(Buffer.from(' \n\t ')) + + await expect( + processDocument( + '/api/files/serve/s3/kb%2Ffixture-blank.txt?context=knowledge-base', + 'blank.txt', + 'text/plain' + ) + ).rejects.toMatchObject({ code: 'no_extractable_text' }) + }) +}) diff --git a/apps/sim/lib/knowledge/search/queries.test.ts b/apps/sim/lib/knowledge/search/queries.test.ts index 1a6170a0a33..a5f20dc7f20 100644 --- a/apps/sim/lib/knowledge/search/queries.test.ts +++ b/apps/sim/lib/knowledge/search/queries.test.ts @@ -591,7 +591,7 @@ describe('live repository authorization follows ranked candidates', () => { rerankPages.length = 0 keywordPages.length = 0 dbChainMockFns.execute.mockImplementation(async (query) => - render(query).sql.includes('CROSS JOIN LATERAL') + render(query).sql.includes('SELECT scoped_chunk.id') ? (probePages.shift() ?? []) : render(query).sql.includes('WITH visible_search_documents') ? (candidatePages.shift() ?? []) @@ -630,6 +630,8 @@ describe('live repository authorization follows ranked candidates', () => { render(query).sql.includes('WITH visible_search_documents') )![0] expect(render(candidateQuery).sql).toContain('MATERIALIZED') + expect(render(candidateQuery).sql).toContain('CROSS JOIN LATERAL') + expect(render(candidateQuery).sql).toContain('LIMIT 1') expect(JSON.stringify(candidateQuery)).toContain('required_clause') expect(JSON.stringify(candidateQuery)).toContain('subvector') const rankQuery = dbChainMockFns.execute.mock.calls.find(([query]) => @@ -721,6 +723,8 @@ describe('live repository authorization follows ranked candidates', () => { )![0] expect(render(candidateQuery).sql).toContain('UNION ALL') expect(render(candidateQuery).sql).toContain('+ 0') + expect(render(candidateQuery).sql).toContain('filtered_scores AS MATERIALIZED') + expect(render(candidateQuery).sql).toContain('ORDER BY filtered_scores.distance + 0') expect(JSON.stringify(dbChainMockFns.where.mock.calls.at(-1)![0])).toContain( 'github_read_grant' ) diff --git a/apps/sim/lib/knowledge/search/queries.ts b/apps/sim/lib/knowledge/search/queries.ts index 46fe5069783..84ace644039 100644 --- a/apps/sim/lib/knowledge/search/queries.ts +++ b/apps/sim/lib/knowledge/search/queries.ts @@ -813,7 +813,8 @@ export async function handleVectorOnlySearch(params: SearchParams): Promise executor.execute<{ id: string; initial_count: number }>(sql` @@ -934,16 +934,32 @@ async function selectLiveVectorResults( WHERE ${and(...candidateDocumentVisibility)} ), initial_candidates AS MATERIALIZED ( SELECT ${embeddingSearch.id} AS id FROM ${embeddingSearch} - WHERE ${candidateConditions} + CROSS JOIN LATERAL ( + SELECT 1 FROM ${document} + WHERE ${and(eq(document.id, embeddingSearch.documentId), ...candidateDocumentVisibility)} + LIMIT 1 + ) AS visible + WHERE ${and( + inArray(embeddingSearch.knowledgeBaseId, params.knowledgeBaseIds), + eq(embeddingSearch.enabled, true) + )} ORDER BY ${candidateDistance} LIMIT ${candidateLimit} + ), filtered_scores AS MATERIALIZED ( + SELECT ${embeddingSearch.id} AS id, ${embeddingSearch.documentId} AS document_id, + ${candidateDistance} AS distance FROM ${embeddingSearch} + WHERE ${and( + inArray(embeddingSearch.knowledgeBaseId, params.knowledgeBaseIds), + eq(embeddingSearch.enabled, true) + )} + AND (SELECT count(*) FROM initial_candidates) < ${candidateLimit} ), candidates AS ( SELECT id FROM initial_candidates WHERE (SELECT count(*) FROM initial_candidates) >= ${candidateLimit} UNION ALL ( - SELECT ${embeddingSearch.id} AS id FROM ${embeddingSearch} - WHERE ${candidateConditions} - AND (SELECT count(*) FROM initial_candidates) < ${candidateLimit} - ORDER BY (${candidateDistance}) + 0, ${embeddingSearch.id} + SELECT filtered_scores.id FROM filtered_scores + INNER JOIN visible_search_documents ON visible_search_documents.id = filtered_scores.document_id + WHERE (SELECT count(*) FROM initial_candidates) < ${candidateLimit} + ORDER BY filtered_scores.distance + 0, filtered_scores.id LIMIT ${candidateLimit} ) ) SELECT id, (SELECT count(*)::int FROM initial_candidates) AS initial_count FROM candidates From 6ea53c0226bd963f762ff840751fde40f15d6ee9 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 16 Sep 2026 17:18:22 -0700 Subject: [PATCH 31/43] fix(slack): allow installation before organization setup (#7900) * fix(slack): allow installation before organization setup * fix(slack): keep installation guidance neutral --- apps/docs/content/docs/search/slack.mdx | 2 + .../slack/oauth/callback/route.test.ts | 104 ++++++++++++++++++ .../knowledge/slack/oauth/callback/route.ts | 35 +++++- .../install/[teamId]/page.test.tsx | 61 ++++++++++ .../slack-search/install/[teamId]/page.tsx | 42 +++++++ apps/sim/lib/api/contracts/knowledge/slack.ts | 2 +- apps/sim/lib/internal/slack/oauth.test.ts | 13 +++ apps/sim/lib/internal/slack/oauth.ts | 7 +- apps/sim/lib/slack-search/install-link.ts | 5 + .../slack-search/public-install-auth.test.ts | 70 ++++++++++++ .../lib/slack-search/public-install-auth.ts | 26 +++++ 11 files changed, 361 insertions(+), 6 deletions(-) create mode 100644 apps/sim/app/api/knowledge/slack/oauth/callback/route.test.ts create mode 100644 apps/sim/app/slack-search/install/[teamId]/page.test.tsx create mode 100644 apps/sim/app/slack-search/install/[teamId]/page.tsx create mode 100644 apps/sim/lib/slack-search/install-link.ts create mode 100644 apps/sim/lib/slack-search/public-install-auth.test.ts create mode 100644 apps/sim/lib/slack-search/public-install-auth.ts diff --git a/apps/docs/content/docs/search/slack.mdx b/apps/docs/content/docs/search/slack.mdx index 26544966a73..4d540e11521 100644 --- a/apps/docs/content/docs/search/slack.mdx +++ b/apps/docs/content/docs/search/slack.mdx @@ -17,6 +17,8 @@ When **Install Sim Search** is available, use the official app: 3. If needed, invite teammates through **Settings → Members → Invite** using their Slack email addresses. App installation does not add people to the Sim organization. 4. Each member opens **Integrations**, selects **Connect** for Slack, and authorizes their own account using the same email as their verified Sim account. +You can also install the official app directly from Slack without choosing a Sim organization or signing in to Sim. When ready, an admin follows the steps above and authorizes the already-installed app for their organization. Sim saves the connection at that point; until then, the bot cannot answer searches. Personal source connections remain separate. + Members use the admin's settings without selecting channels again. Public and private channels are included by default; DMs are opt-in. The bot installation alone does not enable Slack as a source or authorize access to members' messages. diff --git a/apps/sim/app/api/knowledge/slack/oauth/callback/route.test.ts b/apps/sim/app/api/knowledge/slack/oauth/callback/route.test.ts new file mode 100644 index 00000000000..59b74751968 --- /dev/null +++ b/apps/sim/app/api/knowledge/slack/oauth/callback/route.test.ts @@ -0,0 +1,104 @@ +/** @vitest-environment node */ +import { authMockFns, dbChainMockFns } from '@sim/testing' +import { NextRequest } from 'next/server' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { OrchestrationError } from '@/lib/core/orchestration/types' + +const m = vi.hoisted(() => ({ + authenticate: vi.fn(), + complete: vi.fn(), + rate: vi.fn(), +})) +vi.mock('@/lib/slack-search/public-install-auth', () => ({ + authenticateSlackPublicInstallation: m.authenticate, +})) +vi.mock('@/lib/knowledge/application/slack-search/setup', () => ({ + completeSlackSearchSetup: { execute: m.complete }, +})) +vi.mock('@/lib/core/rate-limiter', async (importOriginal) => ({ + ...(await importOriginal()), + enforceIpRateLimit: m.rate, + enforceUserRateLimit: m.rate, +})) +vi.mock('@/lib/core/utils/urls', () => ({ + getBaseUrl: () => 'https://www.sim.ai', + SITE_URL: 'https://www.sim.ai', +})) + +import { GET, HEAD } from '@/app/api/knowledge/slack/oauth/callback/route' + +const request = (query: string) => + new NextRequest(`https://www.sim.ai/api/knowledge/slack/oauth/callback?${query}`) +beforeEach(() => { + vi.clearAllMocks() + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'admin' }, + session: { id: 'session' }, + }) + m.rate.mockResolvedValue(null) + m.authenticate.mockResolvedValue({ teamId: 'T1' }) + m.complete.mockResolvedValue({ organizationId: 'org1' }) +}) +describe('Slack OAuth callback', () => { + it.each(['code=code', 'state=&code=code'])( + 'accepts Slack-initiated install without Sim login: %s', + async (query) => { + authMockFns.mockGetSession.mockResolvedValue(null) + const response = await GET(request(query)) + expect(response.status).toBe(303) + expect(response.headers.get('location')).toBe('https://www.sim.ai/slack-search/install/T1') + expect(response.headers.get('cache-control')).toBe('no-store') + expect(response.headers.get('referrer-policy')).toBe('no-referrer') + expect(authMockFns.mockGetSession).not.toHaveBeenCalled() + expect(dbChainMockFns.insert).not.toHaveBeenCalled() + expect(dbChainMockFns.update).not.toHaveBeenCalled() + expect(m.complete).not.toHaveBeenCalled() + } + ) + it('does not attach a public grant to an existing browser session', async () => { + await GET(request('code=code&organizationId=attacker')) + expect(authMockFns.mockGetSession).not.toHaveBeenCalled() + expect(dbChainMockFns.insert).not.toHaveBeenCalled() + expect(dbChainMockFns.update).not.toHaveBeenCalled() + expect(m.complete).not.toHaveBeenCalled() + }) + it('keeps org-initiated installs on the existing session/state path', async () => { + const response = await GET(request('state=state&code=code')) + expect(response.status).toBe(303) + expect(response.headers.get('location')).toBe( + 'https://www.sim.ai/o/org1/settings/search-slack?slackSetup=complete' + ) + expect(m.complete).toHaveBeenCalledWith( + expect.objectContaining({ + principal: { kind: 'session', userId: 'admin', sessionId: 'session' }, + input: { state: 'state', code: 'code', error: undefined }, + }) + ) + expect(m.authenticate).not.toHaveBeenCalled() + }) + it('never falls back to public install on an invalid nonempty state', async () => { + m.complete.mockRejectedValueOnce(new OrchestrationError('validation', 'Expired state')) + expect((await GET(request('state=expired&code=code'))).status).toBe(400) + expect(m.authenticate).not.toHaveBeenCalled() + }) + it('still requires a Sim session for an org-bound state', async () => { + authMockFns.mockGetSession.mockResolvedValue(null) + expect((await GET(request('state=state&code=code'))).status).toBe(401) + expect(m.authenticate).not.toHaveBeenCalled() + expect(m.complete).not.toHaveBeenCalled() + }) + it.each(['error=access_denied', '', 'state=&state=other&code=code', 'code=a&code=b'])( + 'rejects ambiguous or denied callbacks: %s', + async (query) => { + expect((await GET(request(query))).status).toBe(400) + expect(m.authenticate).not.toHaveBeenCalled() + expect(m.complete).not.toHaveBeenCalled() + } + ) + it('does not consume codes on HEAD requests or after rate limiting', async () => { + expect((await HEAD(request('code=code'))).status).toBe(405) + m.rate.mockResolvedValue(new Response(null, { status: 429 })) + expect((await GET(request('code=code'))).status).toBe(429) + expect(m.authenticate).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/app/api/knowledge/slack/oauth/callback/route.ts b/apps/sim/app/api/knowledge/slack/oauth/callback/route.ts index c9f507625e2..b6155d6f5c3 100644 --- a/apps/sim/app/api/knowledge/slack/oauth/callback/route.ts +++ b/apps/sim/app/api/knowledge/slack/oauth/callback/route.ts @@ -7,24 +7,50 @@ import { internalRateLimits, internalSessionAuth, } from '@/lib/api/server/routes' +import { OrchestrationError } from '@/lib/core/orchestration/types' +import { enforceIpRateLimit } from '@/lib/core/rate-limiter' import { getBaseUrl } from '@/lib/core/utils/urls' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { completeSlackSearchSetup } from '@/lib/knowledge/application/slack-search/setup' import { organizationRoutes } from '@/lib/navigation/paths' +import { slackSearchInstallPath } from '@/lib/slack-search/install-link' +import { authenticateSlackPublicInstallation } from '@/lib/slack-search/public-install-auth' /** OAuth is a redirect protocol; protected configuration remains in the application use case. */ export const GET = withRouteHandler(async (request) => { try { + const limited = await enforceIpRateLimit('slack-search-oauth-callback', request) + if (limited) return limited + const parsed = await parseRequest( + slackSearchOAuthCallbackContract, + request, + {}, + { + rejectDuplicateQueryValues: true, + } + ) + if (!parsed.success) return parsed.response + const { state, code, error } = parsed.data.query + if (!state) { + if (error || !code) + throw new OrchestrationError( + 'validation', + 'Slack installation was not authorized. Install the app again.' + ) + const { teamId } = await authenticateSlackPublicInstallation(code) + return NextResponse.redirect(new URL(slackSearchInstallPath(teamId), getBaseUrl()), { + status: 303, + headers: { 'Cache-Control': 'no-store', 'Referrer-Policy': 'no-referrer' }, + }) + } const principal = await internalSessionAuth.authenticate() const rateResponse = await internalRateLimits .user({ bucketName: 'slack-search-settings' }) .enforce(request, principal) if (rateResponse) return rateResponse - const parsed = await parseRequest(slackSearchOAuthCallbackContract, request, {}) - if (!parsed.success) return parsed.response const result = await completeSlackSearchSetup.execute({ principal, - input: parsed.data.query, + input: { state, code, error }, request, }) const url = new URL( @@ -44,3 +70,6 @@ export const GET = withRouteHandler(async (request) => { throw error } }) + +/** Link previews must not consume a single-use OAuth code. */ +export const HEAD = withRouteHandler(async () => new NextResponse(null, { status: 405 })) diff --git a/apps/sim/app/slack-search/install/[teamId]/page.test.tsx b/apps/sim/app/slack-search/install/[teamId]/page.test.tsx new file mode 100644 index 00000000000..32b82014180 --- /dev/null +++ b/apps/sim/app/slack-search/install/[teamId]/page.test.tsx @@ -0,0 +1,61 @@ +/** @vitest-environment node */ +import type { ComponentProps, ReactNode } from 'react' +import { authMockFns } from '@sim/testing' +import { renderToStaticMarkup } from 'react-dom/server' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const m = vi.hoisted(() => ({ app: vi.fn() })) +vi.mock('@sim/emcn', () => ({ + ChipLink: ({ children, href }: ComponentProps<'a'>) => {children}, +})) +vi.mock('@/app/(auth)/components', () => ({ + AuthShell: ({ children }: { children: ReactNode }) =>
{children}
, +})) +vi.mock('@/lib/core/config/env-flags', () => ({ isHosted: true })) +vi.mock('@/lib/slack-search/shared-app-env', () => ({ + getSharedSlackSearchAppConfiguration: m.app, +})) +vi.mock('next/navigation', () => ({ + notFound: () => { + throw new Error('Not found') + }, +})) + +import SlackInstallPage from '@/app/slack-search/install/[teamId]/page' + +beforeEach(() => { + vi.clearAllMocks() + m.app.mockReturnValue({ id: 'A1' }) + authMockFns.mockGetSession.mockResolvedValue(null) +}) +describe('Slack-initiated install entry', () => { + it('shows setup guidance without asserting installation or requiring sign-in', async () => { + const markup = renderToStaticMarkup( + await SlackInstallPage({ params: Promise.resolve({ teamId: 'T1' }) }) + ) + expect(markup).toContain('Sim Search in Slack') + expect(markup).not.toContain('is installed') + expect(markup).toContain('https://slack.com/app_redirect?app=A1&team=T1') + expect(markup).toContain('href="/home"') + expect(markup).not.toContain('/login') + expect(authMockFns.mockGetSession).not.toHaveBeenCalled() + }) + it('does not infer an organization from an existing Sim session', async () => { + authMockFns.mockGetSession.mockResolvedValue({ user: { id: 'user' } }) + const markup = renderToStaticMarkup( + await SlackInstallPage({ params: Promise.resolve({ teamId: 'T1' }) }) + ) + expect(markup).toContain('connect this workspace later') + expect(authMockFns.mockGetSession).not.toHaveBeenCalled() + }) + it('rejects malformed workspace hints and unavailable apps before reading a session', async () => { + await expect( + SlackInstallPage({ params: Promise.resolve({ teamId: 'https://attacker.test' }) }) + ).rejects.toThrow('Not found') + m.app.mockReturnValue(null) + await expect(SlackInstallPage({ params: Promise.resolve({ teamId: 'T1' }) })).rejects.toThrow( + 'Not found' + ) + expect(authMockFns.mockGetSession).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/app/slack-search/install/[teamId]/page.tsx b/apps/sim/app/slack-search/install/[teamId]/page.tsx new file mode 100644 index 00000000000..98b4cd34894 --- /dev/null +++ b/apps/sim/app/slack-search/install/[teamId]/page.tsx @@ -0,0 +1,42 @@ +import { ChipLink } from '@sim/emcn' +import type { Metadata } from 'next' +import { notFound } from 'next/navigation' +import { isHosted } from '@/lib/core/config/env-flags' +import { APP_ENTRY_PATH } from '@/lib/navigation/paths' +import { getSharedSlackSearchAppConfiguration } from '@/lib/slack-search/shared-app-env' +import { AuthShell } from '@/app/(auth)/components' + +export const metadata: Metadata = { + title: 'Sim Search in Slack', + robots: { index: false, follow: false }, + referrer: 'no-referrer', +} + +interface SlackInstallPageProps { + params: Promise<{ teamId: string }> +} + +export default async function SlackInstallPage({ params }: SlackInstallPageProps) { + const { teamId } = await params + const app = isHosted ? getSharedSlackSearchAppConfiguration() : null + if (!/^T[A-Z0-9]{1,199}$/.test(teamId) || !app) notFound() + const slackUrl = new URL('https://slack.com/app_redirect') + slackUrl.search = new URLSearchParams({ app: app.id, team: teamId }).toString() + return ( + +
+

Sim Search in Slack

+

+ To start searching, an admin can connect this workspace later from Settings → Sim Search + in Slack in their Sim organization. +

+
+ + Open Slack + + Open Sim +
+
+
+ ) +} diff --git a/apps/sim/lib/api/contracts/knowledge/slack.ts b/apps/sim/lib/api/contracts/knowledge/slack.ts index f957b04eb3e..71751e603c4 100644 --- a/apps/sim/lib/api/contracts/knowledge/slack.ts +++ b/apps/sim/lib/api/contracts/knowledge/slack.ts @@ -92,7 +92,7 @@ export const startSlackSearchOAuthContract = defineRouteContract({ }) export const slackSearchOAuthCallbackQuerySchema = z.object({ - state: z.string().min(1).max(200), + state: z.string().max(200).optional(), code: z.string().min(1).max(2000).optional(), error: z.string().min(1).max(200).optional(), }) diff --git a/apps/sim/lib/internal/slack/oauth.test.ts b/apps/sim/lib/internal/slack/oauth.test.ts index 9e4deeb48cf..a8d5d6cca6f 100644 --- a/apps/sim/lib/internal/slack/oauth.test.ts +++ b/apps/sim/lib/internal/slack/oauth.test.ts @@ -28,6 +28,19 @@ beforeEach(() => { fetchMock.mockReset().mockResolvedValue(Response.json(grant)) }) describe('Slack bot OAuth exchange', () => { + it('uses the registered default callback for Slack-initiated installs and discards personal grants', async () => { + fetchMock.mockResolvedValueOnce( + Response.json({ ...grant, authed_user: { access_token: 'personal-token' } }) + ) + expect( + await exchangeSlackBotAuthorization({ + clientId: 'client', + clientSecret: 'secret', + code: 'code', + }) + ).toEqual(grant) + expect(fetchMock.mock.calls[0][1].body.has('redirect_uri')).toBe(false) + }) it('exchanges a code with the same callback and client authentication', async () => { expect(await exchangeSlackBotAuthorization(input)).toEqual(grant) const [url, request] = fetchMock.mock.calls[0] diff --git a/apps/sim/lib/internal/slack/oauth.ts b/apps/sim/lib/internal/slack/oauth.ts index ced0697443a..8d1f1dcccb1 100644 --- a/apps/sim/lib/internal/slack/oauth.ts +++ b/apps/sim/lib/internal/slack/oauth.ts @@ -23,7 +23,7 @@ export async function exchangeSlackBotAuthorization(input: { clientId: string clientSecret: string code: string - redirectUri: string + redirectUri?: string }) { const response = await fetch('https://slack.com/api/oauth.v2.access', { method: 'POST', @@ -31,7 +31,10 @@ export async function exchangeSlackBotAuthorization(input: { Authorization: `Basic ${Buffer.from(`${input.clientId}:${input.clientSecret}`).toString('base64')}`, 'Content-Type': 'application/x-www-form-urlencoded', }, - body: new URLSearchParams({ code: input.code, redirect_uri: input.redirectUri }), + body: new URLSearchParams({ + code: input.code, + ...(input.redirectUri ? { redirect_uri: input.redirectUri } : {}), + }), signal: AbortSignal.timeout(10_000), }) const value = await readResponseJsonWithLimit(response, { diff --git a/apps/sim/lib/slack-search/install-link.ts b/apps/sim/lib/slack-search/install-link.ts new file mode 100644 index 00000000000..11e0e7c2421 --- /dev/null +++ b/apps/sim/lib/slack-search/install-link.ts @@ -0,0 +1,5 @@ +/** The team is a selection hint; linking requires a fresh admin-bound Slack authorization. */ +export function slackSearchInstallPath(teamId: string) { + if (!/^T[A-Z0-9]{1,199}$/.test(teamId)) throw new Error('Invalid Slack workspace ID') + return `/slack-search/install/${teamId}` +} diff --git a/apps/sim/lib/slack-search/public-install-auth.test.ts b/apps/sim/lib/slack-search/public-install-auth.test.ts new file mode 100644 index 00000000000..be556dd5201 --- /dev/null +++ b/apps/sim/lib/slack-search/public-install-auth.test.ts @@ -0,0 +1,70 @@ +/** @vitest-environment node */ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { SLACK_SHARED_SEARCH_BOT_SCOPES } from '@/lib/slack-search/constants' + +const m = vi.hoisted(() => ({ app: vi.fn(), exchange: vi.fn(), hosted: true })) +vi.mock('@/lib/core/config/env-flags', () => ({ + get isHosted() { + return m.hosted + }, +})) +vi.mock('@/lib/slack-search/shared-app-env', () => ({ + getSharedSlackSearchAppConfiguration: m.app, +})) +vi.mock('@/lib/internal/slack/oauth', async (importOriginal) => ({ + ...(await importOriginal()), + exchangeSlackBotAuthorization: m.exchange, +})) + +import { authenticateSlackPublicInstallation } from '@/lib/slack-search/public-install-auth' + +const grant = { + ok: true, + app_id: 'A1', + token_type: 'bot', + access_token: 'bot-token', + bot_user_id: 'UBOT', + scope: SLACK_SHARED_SEARCH_BOT_SCOPES.join(','), + team: { id: 'T1', name: 'Test' }, +} +beforeEach(() => { + vi.clearAllMocks() + m.hosted = true + m.app.mockReturnValue({ id: 'A1', clientId: 'client', clientSecret: 'secret', revision: 'r1' }) + m.exchange.mockResolvedValue(grant) +}) +describe('Slack-initiated installation authentication', () => { + it('completes the Slack install without returning tokens or asserting a Sim identity', async () => { + const result = await authenticateSlackPublicInstallation('one-use-code') + expect(m.exchange).toHaveBeenCalledWith({ + clientId: 'client', + clientSecret: 'secret', + code: 'one-use-code', + }) + expect(result).toEqual({ teamId: 'T1' }) + }) + it.each([ + { app_id: 'A2' }, + { scope: 'chat:write' }, + { is_enterprise_install: true }, + { refresh_token: 'refresh' }, + ])('rejects incompatible grants: %j', async (change) => { + m.exchange.mockResolvedValue({ ...grant, ...change }) + await expect(authenticateSlackPublicInstallation('code')).rejects.toThrow() + }) + it('fails on an expired or replayed provider code', async () => { + m.exchange.mockRejectedValue(new Error('Slack authorization failed')) + await expect(authenticateSlackPublicInstallation('used-code')).rejects.toThrow( + 'Slack authorization failed' + ) + }) + it.each(['self-hosted', 'unconfigured'])( + 'rejects %s deployments before exchange', + async (deployment) => { + if (deployment === 'self-hosted') m.hosted = false + else m.app.mockReturnValue(null) + await expect(authenticateSlackPublicInstallation('code')).rejects.toThrow('unavailable') + expect(m.exchange).not.toHaveBeenCalled() + } + ) +}) diff --git a/apps/sim/lib/slack-search/public-install-auth.ts b/apps/sim/lib/slack-search/public-install-auth.ts new file mode 100644 index 00000000000..6535c17fd4d --- /dev/null +++ b/apps/sim/lib/slack-search/public-install-auth.ts @@ -0,0 +1,26 @@ +import { isHosted } from '@/lib/core/config/env-flags' +import { OrchestrationError } from '@/lib/core/orchestration/types' +import { + exchangeSlackBotAuthorization, + validateSlackBotAuthorization, +} from '@/lib/internal/slack/oauth' +import { SLACK_SHARED_SEARCH_BOT_SCOPES } from '@/lib/slack-search/constants' +import { getSharedSlackSearchAppConfiguration } from '@/lib/slack-search/shared-app-env' + +/** + * Completes installation in Slack without binding it to Sim or retaining tokens. + * Organization setup later obtains its own grant through the admin's state-bound OAuth flow. + */ +export async function authenticateSlackPublicInstallation(code: string) { + const app = isHosted ? getSharedSlackSearchAppConfiguration() : null + if (!app) throw new OrchestrationError('forbidden', 'The Sim Search app is unavailable') + const grant = await exchangeSlackBotAuthorization({ + clientId: app.clientId, + clientSecret: app.clientSecret, + code, + }) + validateSlackBotAuthorization(grant, SLACK_SHARED_SEARCH_BOT_SCOPES) + if (grant.app_id !== app.id) + throw new OrchestrationError('forbidden', 'Slack returned a different app') + return { teamId: grant.team.id } +} From a730e59fd1a7a7976fa14107d35444baf4774031 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 16 Sep 2026 17:32:16 -0700 Subject: [PATCH 32/43] fix(workspace-fork): scope and batch preview revisions (#7901) --- .github/workflows/test-build.yml | 6 + .../application/revision.postgres.test.ts | 180 ++++++++++++++++++ .../application/revision.test.ts | 111 +++++++++++ .../workspace-forking/application/revision.ts | 53 ++++-- .../workspace-forking/lib/copy/copy-files.ts | 5 +- .../sim/lib/workflows/references/resources.ts | 9 +- apps/sim/lib/workspace-files/query-scope.ts | 11 ++ 7 files changed, 352 insertions(+), 23 deletions(-) create mode 100644 apps/sim/ee/workspace-forking/application/revision.postgres.test.ts create mode 100644 apps/sim/ee/workspace-forking/application/revision.test.ts create mode 100644 apps/sim/lib/workspace-files/query-scope.ts diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 61bbfdb56fd..bb504e22fe2 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -121,6 +121,12 @@ jobs: BILLING_USAGE_TEST_REDIS_URL: redis://127.0.0.1:6379 run: bunx vitest run lib/billing/core/usage-log.postgres.test.ts lib/billing/core/organization-activity.postgres.test.ts lib/billing/calculations/usage-reservation.test.ts + - name: Verify fork previews ignore execution file history in PostgreSQL + working-directory: apps/sim + env: + FORK_REVISION_TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/sim_auth_scim + run: bunx vitest run ee/workspace-forking/application/revision.postgres.test.ts + - name: Verify cumulative billing timeout recovery on PostgreSQL 16 if: matrix.provision == 'push' working-directory: apps/sim diff --git a/apps/sim/ee/workspace-forking/application/revision.postgres.test.ts b/apps/sim/ee/workspace-forking/application/revision.postgres.test.ts new file mode 100644 index 00000000000..dee42d3ad93 --- /dev/null +++ b/apps/sim/ee/workspace-forking/application/revision.postgres.test.ts @@ -0,0 +1,180 @@ +/** + * @vitest-environment node + * + * Set FORK_REVISION_TEST_DATABASE_URL to a local PostgreSQL database. Each run uses an + * isolated schema and executes the real revision query against execution-heavy workspaces. + */ +import * as schema from '@sim/db/schema' +import { generateShortId } from '@sim/utils/id' +import { drizzle } from 'drizzle-orm/postgres-js' +import postgres from 'postgres' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { + assertForkPreviewFresh, + loadForkPreviewRevision, +} from '@/ee/workspace-forking/application/revision' + +vi.unmock('drizzle-orm') +vi.unmock('@sim/db/schema') + +const databaseUrl = process.env.FORK_REVISION_TEST_DATABASE_URL +if (databaseUrl && !['localhost', '127.0.0.1', '[::1]'].includes(new URL(databaseUrl).hostname)) { + throw new Error('Fork revision PostgreSQL tests require a local database') +} + +describe.runIf(Boolean(databaseUrl))('fork revision scope in PostgreSQL', () => { + const testSchema = `fork_revision_${generateShortId() + .replace(/[^a-zA-Z0-9]/g, '') + .toLowerCase()}` + let client: ReturnType + let executor: ReturnType> + const scope = { + sourceWorkspaceId: 'source', + targetWorkspaceId: 'target', + edge: { parentWorkspaceId: 'target', childWorkspaceId: 'source' }, + } + + beforeAll(async () => { + client = postgres(databaseUrl!, { max: 1, connection: { search_path: testSchema } }) + executor = drizzle(client, { schema }) + await client.unsafe(`CREATE SCHEMA ${testSchema}`) + await client.unsafe(` + CREATE TABLE workspace ( + id text PRIMARY KEY, organization_id text, name text, + storage_used_bytes bigint DEFAULT 0, updated_at timestamp + ); + CREATE TABLE workflow ( + id text PRIMARY KEY, workspace_id text, name text, archived_at timestamp, + fork_sync_excluded boolean DEFAULT false, is_deployed boolean DEFAULT true, + run_count integer DEFAULT 0, last_run_at timestamp, updated_at timestamp + ); + CREATE TABLE workflow_deployment_version ( + id text PRIMARY KEY, workflow_id text, is_active boolean, state jsonb + ); + CREATE TABLE workspace_files ( + id text PRIMARY KEY, workspace_id text, context text, deleted_at timestamp, + key text, original_name text, size_bytes bigint, content_updated_at timestamp + ); + CREATE INDEX ON workspace_files (workspace_id) + WHERE context = 'workspace' AND deleted_at IS NULL; + CREATE TABLE permissions (id text PRIMARY KEY, entity_id text, entity_type text, permission_type text); + CREATE TABLE custom_block (id text PRIMARY KEY, organization_id text, workflow_id text); + `) + for (const table of ['workflow_blocks', 'workflow_edges', 'workflow_subflows', 'webhook']) { + await client.unsafe( + `CREATE TABLE ${table} (id text PRIMARY KEY, workflow_id text, data jsonb)` + ) + } + for (const table of [ + 'folder', + 'user_table_definitions', + 'knowledge_base', + 'custom_tools', + 'skill', + 'mcp_servers', + 'credential', + 'workspace_environment', + 'workspace_sandbox', + ]) { + await client.unsafe( + `CREATE TABLE ${table} (id text PRIMARY KEY, workspace_id text, data jsonb)` + ) + } + for (const table of [ + 'workspace_fork_resource_map', + 'workspace_fork_block_map', + 'workspace_fork_dependent_value', + ]) { + await client.unsafe( + `CREATE TABLE ${table} (id text PRIMARY KEY, child_workspace_id text, data jsonb)` + ) + } + await client`INSERT INTO workspace (id, name) VALUES ('source', 'Source'), ('target', 'Target')` + await client`INSERT INTO workflow (id, workspace_id, name) + VALUES ('source-workflow', 'source', 'Source workflow'), ('target-workflow', 'target', 'Target workflow')` + await client`INSERT INTO workflow_deployment_version (id, workflow_id, is_active, state) + VALUES ('deployment', 'source-workflow', true, '{"blocks":{}}')` + }) + + afterAll(async () => { + if (!client) return + await client.unsafe(`DROP SCHEMA IF EXISTS ${testSchema} CASCADE`) + await client.end() + }) + + beforeEach(async () => { + await client`TRUNCATE workspace_files, workflow_blocks, permissions` + await client`UPDATE workspace SET storage_used_bytes = 0, updated_at = null` + await client`INSERT INTO workspace_files + (id, workspace_id, context, key, original_name, size_bytes, content_updated_at) + VALUES ('source-file', 'source', 'workspace', 'workspace/source/file', 'file.txt', 24, '2026-09-01'), + ('target-file', 'target', 'workspace', 'workspace/target/file', 'file.txt', 24, '2026-09-01')` + }) + + it('ignores more than 100,000 execution files, deleted files, chat uploads, and runtime storage changes', async () => { + const before = await loadForkPreviewRevision(executor, scope, {}) + await client`INSERT INTO workspace_files (id, workspace_id, context, key, original_name, size_bytes) + SELECT 'execution-' || n, CASE WHEN n % 2 = 0 THEN 'source' ELSE 'target' END, + 'execution', 'execution/' || n, repeat('x', 700), 128 + FROM generate_series(1, 100001) n` + await client`INSERT INTO workspace_files (id, workspace_id, context, deleted_at) + VALUES ('deleted', 'source', 'workspace', now()), ('upload', 'target', 'mothership', null), + ('kb-document', 'source', 'knowledge-base', null), ('other-workspace', 'unrelated', 'workspace', null)` + await client`UPDATE workspace SET storage_used_bytes = 999999, updated_at = now()` + await client`UPDATE workflow SET run_count = run_count + 1, last_run_at = now(), updated_at = now()` + + const after = await loadForkPreviewRevision(executor, scope, {}) + expect(after).toEqual(before) + await expect( + assertForkPreviewFresh(executor, scope, { + workspaceId: 'source', + requestId: 'request', + requestHash: 'hash', + previewFingerprint: before.fingerprint, + choices: {}, + }) + ).resolves.toBeUndefined() + }) + + it.each(['source', 'target'])( + 'invalidates previews when an active %s file changes or disappears', + async (workspaceId) => { + const before = await loadForkPreviewRevision(executor, scope, {}) + await client`UPDATE workspace_files SET content_updated_at = '2026-09-02' WHERE workspace_id = ${workspaceId}` + const edited = await loadForkPreviewRevision(executor, scope, {}) + expect(edited.categories.files).not.toBe(before.categories.files) + + await client`UPDATE workspace_files SET deleted_at = now() WHERE workspace_id = ${workspaceId}` + const deleted = await loadForkPreviewRevision(executor, scope, {}) + expect(deleted.categories.files).not.toBe(edited.categories.files) + + await client`UPDATE workspace_files SET deleted_at = null WHERE workspace_id = ${workspaceId}` + expect((await loadForkPreviewRevision(executor, scope, {})).fingerprint).toBe( + edited.fingerprint + ) + } + ) + + it('still detects graph edits, access changes, and changed copy choices', async () => { + const before = await loadForkPreviewRevision(executor, scope, {}) + await client`INSERT INTO workflow_blocks (id, workflow_id, data) + VALUES ('block', 'target-workflow', '{"value":"changed"}')` + await client`INSERT INTO permissions (id, entity_id, entity_type, permission_type) + VALUES ('member', 'source', 'workspace', 'admin')` + const after = await loadForkPreviewRevision(executor, scope, {}) + expect(after.categories.target_graph).not.toBe(before.categories.target_graph) + expect(after.categories.membership).not.toBe(before.categories.membership) + expect( + (await loadForkPreviewRevision(executor, scope, { copyResources: [] })).fingerprint + ).not.toBe(after.fingerprint) + }) + + it('retains the row limit for actual fork resources', async () => { + await client`INSERT INTO workspace_files (id, workspace_id, context) + SELECT 'durable-' || n, 'source', 'workspace' FROM generate_series(1, 100001) n` + await expect(loadForkPreviewRevision(executor, scope, {})).rejects.toMatchObject({ + statusCode: 413, + message: 'Fork preview files exceeds its 100000 row ceiling', + }) + }) +}) diff --git a/apps/sim/ee/workspace-forking/application/revision.test.ts b/apps/sim/ee/workspace-forking/application/revision.test.ts new file mode 100644 index 00000000000..bfd8b911ec5 --- /dev/null +++ b/apps/sim/ee/workspace-forking/application/revision.test.ts @@ -0,0 +1,111 @@ +/** @vitest-environment node */ +import type { SQL } from 'drizzle-orm' +import { PgDialect } from 'drizzle-orm/pg-core' +import { describe, expect, it, vi } from 'vitest' +import type { DbOrTx } from '@/lib/db/types' +import { WorkspaceOperationConflict } from '@/lib/workspaces/operations/receipts' +import { + assertForkPreviewFresh, + loadForkPreviewRevision, +} from '@/ee/workspace-forking/application/revision' + +vi.unmock('drizzle-orm') +vi.unmock('@sim/db/schema') + +const scope = { + sourceWorkspaceId: 'source', + targetWorkspaceId: 'target', + edge: { parentWorkspaceId: 'target', childWorkspaceId: 'source' }, +} + +function mockRevisionExecutor(overrides: { count?: string; bytes?: string; digest?: string } = {}) { + const execute = vi.fn(async (_query: SQL) => [ + { category: 'files', count: '3', bytes: '1024', digest: 'file-revision', ...overrides }, + ]) + return { execute, executor: { execute } as unknown as DbOrTx } +} + +describe('fork preview revisions', () => { + it('reads every category in one bounded query and excludes files the sync cannot copy', async () => { + const { execute, executor } = mockRevisionExecutor() + await loadForkPreviewRevision(executor, scope, {}) + + expect(execute).toHaveBeenCalledTimes(1) + const query = new PgDialect().sqlToQuery(execute.mock.calls[0][0]) + expect(query.sql).toMatch(/"workspace_files"\."context" = \$\d+/) + expect(query.sql).toContain('"workspace_files"."deleted_at" is null') + expect(query.params).toContain('workspace') + expect(query.sql).toContain("ARRAY['updated_at', 'storage_used_bytes']") + expect(query.sql.match(/LIMIT \$\d+/g)).toHaveLength(20) + expect(query.params.filter((value) => value === 100_001)).toHaveLength(20) + expect(query.params).toContain('source') + expect(query.params).toContain('target') + expect(query.params).toContain('mappings') + expect(query.params).toContain('block_identities') + expect(query.params).toContain('dependent_values') + expect(query.params.some(Array.isArray)).toBe(false) + }) + + it('supports creating a fork without a target or existing edge', async () => { + const { execute, executor } = mockRevisionExecutor() + await loadForkPreviewRevision(executor, { sourceWorkspaceId: 'source' }, {}) + + const query = new PgDialect().sqlToQuery(execute.mock.calls[0][0]) + expect(query.sql.match(/LIMIT \$\d+/g)).toHaveLength(17) + expect(query.params).not.toContain('target') + expect(query.params).not.toContain('mappings') + expect(query.params.some((value) => value == null)).toBe(false) + }) + + it.each([ + { count: '100001', bytes: '1', message: 'Fork preview files exceeds its 100000 row ceiling' }, + { + count: '1', + bytes: String(64 * 1024 * 1024 + 1), + message: 'Fork preview files exceeds its 64 MiB byte ceiling', + }, + ])('rejects an oversized category with the actual limiting budget: $message', async (row) => { + const { executor } = mockRevisionExecutor(row) + await expect(loadForkPreviewRevision(executor, scope, {})).rejects.toMatchObject({ + statusCode: 413, + message: row.message, + }) + }) + + it('accepts categories exactly at both limits', async () => { + const { executor } = mockRevisionExecutor({ count: '100000', bytes: String(64 * 1024 * 1024) }) + await expect(loadForkPreviewRevision(executor, scope, {})).resolves.toMatchObject({ + categories: { files: 'file-revision' }, + }) + }) + + it('keeps scope and copy choices bound to the fingerprint', async () => { + const { executor } = mockRevisionExecutor() + const original = await loadForkPreviewRevision(executor, scope, {}) + const changedScope = await loadForkPreviewRevision( + executor, + { ...scope, targetWorkspaceId: 'other-target' }, + {} + ) + const changedChoices = await loadForkPreviewRevision(executor, scope, { copyResources: [] }) + expect(changedScope.fingerprint).not.toBe(original.fingerprint) + expect(changedChoices.fingerprint).not.toBe(original.fingerprint) + }) + + it('still refuses apply when a reviewed resource changes', async () => { + const { executor, execute } = mockRevisionExecutor() + const preview = await loadForkPreviewRevision(executor, scope, {}) + const admission = { + workspaceId: 'source', + requestId: 'request', + requestHash: 'request-hash', + previewFingerprint: preview.fingerprint, + choices: {}, + } + await expect(assertForkPreviewFresh(executor, scope, admission)).resolves.toBeUndefined() + execute.mockResolvedValue([{ category: 'files', count: '3', bytes: '1024', digest: 'changed' }]) + await expect(assertForkPreviewFresh(executor, scope, admission)).rejects.toBeInstanceOf( + WorkspaceOperationConflict + ) + }) +}) diff --git a/apps/sim/ee/workspace-forking/application/revision.ts b/apps/sim/ee/workspace-forking/application/revision.ts index 2bb20288ae4..3c27d3c592e 100644 --- a/apps/sim/ee/workspace-forking/application/revision.ts +++ b/apps/sim/ee/workspace-forking/application/revision.ts @@ -22,9 +22,10 @@ import { workspaceForkResourceMap, workspaceSandbox, } from '@sim/db/schema' -import { type SQL, sql } from 'drizzle-orm' +import { and, type SQL, sql } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' import { acquireFolderMutationLock } from '@/lib/folders/locks' +import { activeWorkspaceFileConditions } from '@/lib/workspace-files/query-scope' import { WorkspaceOperationConflict, workflowOperationFingerprint, @@ -46,7 +47,14 @@ export interface ForkMutationAdmission { choices: Record } -/** Digests are bounded database aggregates; graph and secret values never enter preview diagnostics. */ +const MAX_REVISION_ROWS = 100_000 +const MAX_REVISION_BYTES = 64 * 1024 * 1024 + +/** + * Fingerprints fork configuration in one database snapshot. Runtime file outputs and their + * storage ledger are not sync inputs; including them makes ordinary executions invalidate + * previews. Only bounded aggregates leave the database, never graph or secret values. + */ export async function loadForkPreviewRevision( executor: DbOrTx, scope: ForkRevisionScope, @@ -64,7 +72,7 @@ export async function loadForkPreviewRevision( ) const workflowIds = sql`SELECT id FROM ${workflow} WHERE workspace_id IN (${values})` const queries: Record = { - workspaces: sql`SELECT id, to_jsonb(r) - ARRAY['updated_at'] AS state FROM ${workspace} r WHERE id IN (${values})`, + workspaces: sql`SELECT id, to_jsonb(r) - ARRAY['updated_at', 'storage_used_bytes'] AS state FROM ${workspace} r WHERE id IN (${values})`, workflows: sql`SELECT id, to_jsonb(r) - ARRAY['run_count', 'last_run_at', 'last_synced', 'updated_at'] AS state FROM ${workflow} r WHERE workspace_id IN (${values})`, source_deployments: sql`SELECT d.id, to_jsonb(d) AS state FROM ${workflowDeploymentVersion} d JOIN ${workflow} w ON w.id = d.workflow_id WHERE w.workspace_id = ${scope.sourceWorkspaceId} AND d.is_active = true AND w.archived_at IS NULL AND w.fork_sync_excluded = false`, target_graph: sql`SELECT 'block:' || b.id AS id, to_jsonb(b) - ARRAY['updated_at', 'created_at'] AS state FROM ${workflowBlocks} b JOIN ${workflow} w ON w.id = b.workflow_id WHERE w.workspace_id = ${scope.targetWorkspaceId ?? scope.sourceWorkspaceId} @@ -78,7 +86,7 @@ export async function loadForkPreviewRevision( tools: sql`SELECT id, to_jsonb(r) AS state FROM ${customTools} r WHERE workspace_id IN (${values})`, skills: sql`SELECT id, to_jsonb(r) AS state FROM ${skill} r WHERE workspace_id IN (${values})`, servers: sql`SELECT id, to_jsonb(r) - ARRAY['updated_at', 'last_connected_at', 'last_tools_refresh', 'tool_count', 'connection_status', 'last_error'] AS state FROM ${mcpServers} r WHERE workspace_id IN (${values})`, - files: sql`SELECT id, to_jsonb(r) AS state FROM ${workspaceFiles} r WHERE workspace_id IN (${values})`, + files: sql`SELECT id, to_jsonb(${workspaceFiles}) AS state FROM ${workspaceFiles} WHERE ${and(...activeWorkspaceFileConditions(ids))}`, credentials: sql`SELECT id, to_jsonb(r) - ARRAY['updated_at', 'last_used_at'] AS state FROM ${credential} r WHERE workspace_id IN (${values})`, secrets: sql`SELECT id, to_jsonb(r) - 'updated_at' AS state FROM ${workspaceEnvironment} r WHERE workspace_id IN (${values})`, sandboxes: sql`SELECT id, to_jsonb(r) AS state FROM ${workspaceSandbox} r WHERE workspace_id IN (${values})`, @@ -89,17 +97,34 @@ export async function loadForkPreviewRevision( queries.block_identities = sql`SELECT id, to_jsonb(r) AS state FROM ${workspaceForkBlockMap} r WHERE child_workspace_id = ${scope.edge.childWorkspaceId}` queries.dependent_values = sql`SELECT id, to_jsonb(r) AS state FROM ${workspaceForkDependentValue} r WHERE child_workspace_id = ${scope.edge.childWorkspaceId}` } - const categories: Record = {} - for (const [category, rows] of Object.entries(queries)) { - const [size] = await executor.execute<{ count: string; bytes: string }>( - sql`SELECT count(*)::text AS count, coalesce(sum(octet_length(state::text)), 0)::text AS bytes FROM (${rows}) revision_rows` - ) - if (Number(size.count) > 100000 || Number(size.bytes) > 64 * 1024 * 1024) - throw new ForkError(`Fork preview ${category} exceeds its row or 64 MiB byte ceiling`, 413) - const [revision] = await executor.execute<{ digest: string }>( - sql`SELECT md5(coalesce(string_agg(md5(state::text), '' ORDER BY id), '')) AS digest FROM (${rows}) revision_rows` + const revisions = await executor.execute<{ + category: string + count: string + bytes: string + digest: string + }>( + sql.join( + Object.entries(queries).map( + ([category, rows]) => sql` + SELECT ${category}::text AS category, count(*)::text AS count, + coalesce(sum(octet_length(state::text)), 0)::text AS bytes, + md5(coalesce(string_agg(md5(state::text), '' ORDER BY id), '')) AS digest + FROM (SELECT id, state FROM (${rows}) revision_source LIMIT ${MAX_REVISION_ROWS + 1}) revision_rows + ` + ), + sql` UNION ALL ` ) - categories[category] = revision.digest + ) + const categories: Record = {} + for (const revision of revisions) { + if (Number(revision.count) > MAX_REVISION_ROWS) + throw new ForkError( + `Fork preview ${revision.category} exceeds its ${MAX_REVISION_ROWS} row ceiling`, + 413 + ) + if (Number(revision.bytes) > MAX_REVISION_BYTES) + throw new ForkError(`Fork preview ${revision.category} exceeds its 64 MiB byte ceiling`, 413) + categories[revision.category] = revision.digest } return { categories, diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts index 1610f6828df..4817ce987b9 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts @@ -22,6 +22,7 @@ import { } from '@/lib/uploads/core/storage-service' import { getWorkspaceFileSize, type StorageContext } from '@/lib/uploads/shared/types' import { MAX_FILE_SIZE } from '@/lib/uploads/utils/validation' +import { activeWorkspaceFileConditions } from '@/lib/workspace-files/query-scope' import { resolveForkFolderMapping } from '@/ee/workspace-forking/lib/copy/copy-workflows' import { assertForkCopyActive, @@ -201,9 +202,7 @@ export async function planForkFileCopies(params: { .where( and( selectors.length === 1 ? selectors[0] : or(...selectors), - eq(workspaceFiles.workspaceId, sourceWorkspaceId), - eq(workspaceFiles.context, 'workspace'), - isNull(workspaceFiles.deletedAt) + ...activeWorkspaceFileConditions([sourceWorkspaceId]) ) ) diff --git a/apps/sim/lib/workflows/references/resources.ts b/apps/sim/lib/workflows/references/resources.ts index f1168ae7d45..e1cff65b43c 100644 --- a/apps/sim/lib/workflows/references/resources.ts +++ b/apps/sim/lib/workflows/references/resources.ts @@ -24,6 +24,7 @@ import { MAX_FOLDERS_PER_WORKSPACE } from '@/lib/folders/constants' import { parseFolderPath, ROOT_FOLDER_PATH } from '@/lib/folders/paths' import { loadActiveFolderPathIndex } from '@/lib/folders/queries' import type { ForkMcpServerMeta, ForkRemapKind } from '@/lib/workflows/references/remap-references' +import { activeWorkspaceFileConditions } from '@/lib/workspace-files/query-scope' export interface ForkResourceCandidate { id: string @@ -238,9 +239,7 @@ const fileCandidatesQuery = (executor: DbOrTx, workspaceId: string, keys?: strin .from(workspaceFiles) .where( and( - eq(workspaceFiles.workspaceId, workspaceId), - eq(workspaceFiles.context, 'workspace'), - isNull(workspaceFiles.deletedAt), + ...activeWorkspaceFileConditions([workspaceId]), keys ? inArray(workspaceFiles.key, keys) : undefined ) ) @@ -300,9 +299,7 @@ const fileCandidatesWithFolderQuery = ( ) .where( and( - eq(workspaceFiles.workspaceId, workspaceId), - eq(workspaceFiles.context, 'workspace'), - isNull(workspaceFiles.deletedAt), + ...activeWorkspaceFileConditions([workspaceId]), page?.after ? gt(workspaceFiles.id, page.after) : undefined, keys ? inArray(workspaceFiles.key, keys) : undefined ) diff --git a/apps/sim/lib/workspace-files/query-scope.ts b/apps/sim/lib/workspace-files/query-scope.ts new file mode 100644 index 00000000000..88c533e53f8 --- /dev/null +++ b/apps/sim/lib/workspace-files/query-scope.ts @@ -0,0 +1,11 @@ +import { workspaceFiles } from '@sim/db/schema' +import { eq, inArray, isNull } from 'drizzle-orm' + +/** Durable files available to workspace resource pickers, reference mappings, and fork copies. */ +export function activeWorkspaceFileConditions(workspaceIds: string[]) { + return [ + inArray(workspaceFiles.workspaceId, workspaceIds), + eq(workspaceFiles.context, 'workspace'), + isNull(workspaceFiles.deletedAt), + ] +} From bf37391d8cbb558b7bfaefefe03f1e17579faad6 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 16 Sep 2026 17:55:21 -0700 Subject: [PATCH 33/43] fix(ci): prepare tasks before app rollout and promote at cutover (#7902) * fix(ci): prepare tasks before app rollout and promote at cutover * fix(ci): correlate retry snapshots before checking failure --- .github/scripts/test-trigger-deploy.py | 153 +++++++++++++++++++++++- .github/scripts/wait-for-ecs-cutover.sh | 35 +++++- .github/workflows/ci.yml | 129 ++++++++------------ 3 files changed, 230 insertions(+), 87 deletions(-) diff --git a/.github/scripts/test-trigger-deploy.py b/.github/scripts/test-trigger-deploy.py index 1ea31f4961a..64febf4b611 100644 --- a/.github/scripts/test-trigger-deploy.py +++ b/.github/scripts/test-trigger-deploy.py @@ -2,6 +2,7 @@ import json import os from pathlib import Path +import re import subprocess import tempfile import unittest @@ -19,6 +20,17 @@ def execution(start=1000, digest=DIGEST, identifier='execution-current'): } +def deploy_action(identifier='action-current', start=1000, status='InProgress'): + return {'actionName': 'Deploy_to_ECS', 'actionExecutionId': identifier, + 'startTime': start, 'status': status, 'output': {}} + + +def deploy_state(execution_id='execution-current', action_id='action-current', deployment_id='d-current'): + return {'latestExecution': {'pipelineExecutionId': execution_id}, 'actionStates': [ + {'actionName': 'Deploy_to_ECS', 'latestExecution': { + 'actionExecutionId': action_id, 'externalExecutionId': deployment_id, 'status': 'InProgress'}}]} + + class DeploymentGateTests(unittest.TestCase): def run_script(self, script, args, responses): with tempfile.TemporaryDirectory() as directory: @@ -88,7 +100,8 @@ def poll(self, updates=None, since='1000'): responses = { 'list-pipeline-executions': {'json': [execution()]}, 'get-pipeline-execution': {'text': 'InProgress'}, - 'list-action-executions': {'text': 'd-current'}, + 'get-pipeline-state': {'json': deploy_state()}, + 'list-action-executions': {'json': [deploy_action()]}, 'get-deployment': {'text': 'InProgress'}, 'list-deployment-targets': {'text': 'target-one\ttarget-two'}, 'get-deployment-target:target-one': {'text': 'Succeeded'}, @@ -175,10 +188,64 @@ def test_failed_and_superseded_pipeline_never_reach_deployment(self): self.assertNotIn('get-deployment ', calls) def test_waits_for_queued_deploy_action(self): - result, calls = self.poll({'list-action-executions': [{'text': 'None'}, {'text': 'd-current'}]}) + result, calls = self.poll({'list-action-executions': [ + {'json': []}, {'json': [deploy_action()]}]}) self.assertEqual(result.returncode, 0, result.stderr) self.assertEqual(calls.count('list-action-executions '), 2) + def test_finds_live_deployment_before_action_history_has_output(self): + result, calls = self.poll() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn('get-pipeline-state --name app-pipeline', calls) + self.assertIn('get-deployment --deployment-id d-current', calls) + self.assertIn('Traffic cutover complete', result.stdout) + + def test_waits_for_state_from_the_exact_pipeline_and_action(self): + for stale in (None, deploy_state(execution_id='execution-old'), + deploy_state(action_id='action-old'), deploy_state(deployment_id='')): + with self.subTest(stale=stale): + result, calls = self.poll({'get-pipeline-state': [ + {'json': stale}, {'json': deploy_state()}]}) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(calls.count('get-pipeline-state '), 2) + self.assertEqual(calls.count('get-deployment '), 1) + + def test_old_live_action_cannot_satisfy_a_retry(self): + result, calls = self.poll({ + 'list-action-executions': {'json': [deploy_action(), deploy_action('action-old', start=999)]}, + 'get-pipeline-state': [ + {'json': deploy_state(action_id='action-old', deployment_id='d-old')}, + {'json': deploy_state()}], + }) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertNotIn('--deployment-id d-old', calls) + + def test_live_retry_waits_for_history_to_include_the_same_attempt(self): + for previous_status in ('Failed', 'Abandoned'): + with self.subTest(previous_status=previous_status): + previous = deploy_action('action-old', start=999, status=previous_status) + result, calls = self.poll({ + 'list-action-executions': [ + {'json': [previous]}, + {'json': [previous, deploy_action()]}, + ], + }) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(calls.count('get-pipeline-state '), 2) + self.assertEqual(calls.count('get-deployment '), 1) + self.assertIn('get-deployment --deployment-id d-current', calls) + + def test_bad_live_state_and_failed_actions_fail_closed(self): + for updates in ( + {'get-pipeline-state': {'error': 'AccessDeniedException'}}, + {'get-pipeline-state': {'json': deploy_state(deployment_id='wrong-provider-id')}}, + {'list-action-executions': {'json': [deploy_action(status='Failed')]}}, + ): + with self.subTest(updates=updates): + result, calls = self.poll(updates) + self.assertNotEqual(result.returncode, 0) + self.assertNotIn('get-deployment ', calls) + def test_failed_deployment_never_accepts_old_cutover(self): result, calls = self.poll({'get-deployment': {'text': 'Failed'}}) self.assertNotEqual(result.returncode, 0) @@ -260,5 +327,87 @@ def test_same_digest_tag_move_reports_unchanged(self): self.assertIn('app_image_changed=false', result.github_output) +class ReleaseOrderingTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + workflow = SCRIPTS.parent / 'workflows' / 'ci.yml' + parsed = subprocess.run([ + 'bun', '-e', 'console.log(JSON.stringify(Bun.YAML.parse(await Bun.file(process.argv[1]).text())))', + str(workflow)], check=True, capture_output=True, text=True) + cls.jobs = json.loads(parsed.stdout)['jobs'] + + def eligible(self, job, branch, results, event='push', cancelled=False, promoted='true'): + expression = self.jobs[job]['if'] + expression = re.sub(r'needs\.([\w-]+)\.result', lambda m: repr(results[m[1]]), expression) + expression = expression.replace('needs.promote-images.outputs.promoted', repr(promoted)) + expression = expression.replace('github.ref', repr('refs/heads/' + branch)) + expression = expression.replace('github.event_name', repr(event)) + expression = expression.replace('!cancelled()', repr(not cancelled)) + expression = expression.replace('&&', ' and ').replace('||', ' or ') + return eval(' '.join(expression.split()), {'__builtins__': {}}) + + def release_results(self, branch): + active = ('migrate-dev', 'build-dev', 'deploy-trigger-dev') if branch == 'dev' else ( + 'migrate', 'build-amd64', 'deploy-trigger') + results = {name: 'success' if name in active else 'skipped' + for name in self.jobs['promote-images']['needs']} + return active, results + + def test_uploads_and_image_builds_can_start_before_migration(self): + for job in ('deploy-trigger', 'deploy-trigger-dev', 'build-amd64', 'build-dev'): + self.assertFalse(self.jobs[job].get('needs'), job) + for job in ('deploy-trigger', 'deploy-trigger-dev'): + upload = next(step for step in self.jobs[job]['steps'] if step.get('id') == 'deploy') + self.assertIn('--skip-promotion', upload['run']) + + def test_each_release_waits_for_all_three_gates(self): + for branch in ('main', 'staging', 'dev'): + active, ready = self.release_results(branch) + self.assertTrue(self.eligible('promote-images', branch, ready)) + for gate in active: + self.assertIn(gate, self.jobs['promote-images']['needs']) + for failure in ('failure', 'cancelled', 'skipped'): + with self.subTest(branch=branch, gate=gate, result=failure): + self.assertFalse(self.eligible('promote-images', branch, {**ready, gate: failure})) + self.assertFalse(self.eligible('promote-images', branch, ready, cancelled=True)) + self.assertFalse(self.eligible('promote-images', branch, ready, event='pull_request')) + + def test_migrations_still_require_successful_tests(self): + self.assertIn('test-build', self.jobs['migrate']['needs']) + for branch in ('main', 'staging'): + self.assertTrue(self.eligible('migrate', branch, {'test-build': 'success'})) + for result in ('failure', 'cancelled', 'skipped'): + self.assertFalse(self.eligible('migrate', branch, {'test-build': result})) + + def test_dev_build_cannot_move_deploy_tags(self): + steps = self.jobs['build-dev']['steps'] + build = next(step for step in steps if step.get('uses') == './.github/actions/docker-build') + self.assertTrue(build['with']['tags'].endswith(':${{ github.sha }}-dev')) + self.assertNotIn('promote-app-image.sh', json.dumps(steps)) + self.assertNotIn('imagetools create', json.dumps(steps)) + + def test_task_promotion_requires_a_successful_fresh_app_release(self): + for branch, job, upload in (('main', 'promote-trigger', 'deploy-trigger'), + ('staging', 'promote-trigger', 'deploy-trigger'), + ('dev', 'promote-trigger-dev', 'deploy-trigger-dev')): + ready = {'promote-images': 'success', upload: 'success'} + self.assertIn('promote-images', self.jobs[job]['needs']) + self.assertTrue(self.eligible(job, branch, ready)) + self.assertFalse(self.eligible(job, branch, ready, promoted='false')) + self.assertFalse(self.eligible(job, branch, {**ready, 'promote-images': 'failure'})) + steps = self.jobs[job]['steps'] + wait = next(i for i, step in enumerate(steps) if 'wait-for-ecs-cutover.sh' in step.get('run', '')) + promote = next(i for i, step in enumerate(steps) if 'promote "$VERSION"' in step.get('run', '')) + self.assertLess(wait, promote) + self.assertEqual(steps[promote]['env']['VERSION'], '${{ needs.' + upload + '.outputs.version }}') + + def test_permission_check_and_other_images_precede_app_rollout(self): + steps = self.jobs['promote-images']['steps'] + preflight = next(i for i, step in enumerate(steps) if 'get-pipeline-state' in step.get('run', '')) + retag = next(i for i, step in enumerate(steps) if step.get('id') == 'promote') + self.assertLess(preflight, retag) + self.assertTrue(steps[retag]['env']['ECR_REPOS'].strip().endswith('${{ secrets.ECR_APP }}')) + + if __name__ == '__main__': unittest.main() diff --git a/.github/scripts/wait-for-ecs-cutover.sh b/.github/scripts/wait-for-ecs-cutover.sh index b18758378fa..4e9b10f5d66 100755 --- a/.github/scripts/wait-for-ecs-cutover.sh +++ b/.github/scripts/wait-for-ecs-cutover.sh @@ -81,10 +81,39 @@ while [ -z "$DEPLOYMENT_ID" ] || [ "$DEPLOYMENT_ID" = 'None' ]; do InProgress|Succeeded) ;; *) log "ERROR: unexpected pipeline status: $status"; exit 1 ;; esac - DEPLOYMENT_ID=$(aws_read codepipeline list-action-executions \ + # Action history does not publish the external deployment ID until cleanup + # finishes. Live state exposes it while traffic is shifting. Correlate both + # the stage execution and action attempt so old state cannot satisfy this run. + deploy_state=$(aws_read codepipeline get-pipeline-state --name "$PIPELINE" \ + --query "stageStates[?stageName=='Deploy'] | [0]" --output json) + deploy_actions=$(aws_read codepipeline list-action-executions \ --pipeline-name "$PIPELINE" --filter pipelineExecutionId="$EXECUTION_ID" \ - --query "actionExecutionDetails[?stageName=='Deploy'].output.executionResult.externalExecutionId | [0]" \ - --output text) + --query "actionExecutionDetails[?stageName=='Deploy']" --output json) + DEPLOYMENT_ID=$(printf '%s\n' "$deploy_actions" | DEPLOY_STATE="$deploy_state" EXECUTION_ID="$EXECUTION_ID" python3 -c ' +import json, os, re, sys +state = json.loads(os.environ["DEPLOY_STATE"]) +actions = json.load(sys.stdin) +if not state or state.get("latestExecution", {}).get("pipelineExecutionId") != os.environ["EXECUTION_ID"] or not actions: + print("") + sys.exit(0) +if len({a["actionName"] for a in actions}) != 1: + raise SystemExit("ERROR: expected one Deploy action in the app pipeline") +latest = max(actions, key=lambda a: a["startTime"]) +matches = [a["latestExecution"] for a in state.get("actionStates", []) + if a["actionName"] == latest["actionName"] + and a.get("latestExecution", {}).get("actionExecutionId") == latest["actionExecutionId"]] +if len(matches) > 1: + raise SystemExit("ERROR: ambiguous live Deploy action") +if not matches: + print("") + sys.exit(0) +if latest["status"] not in ("InProgress", "Succeeded"): + raise SystemExit("ERROR: Deploy action ended in " + latest["status"]) +deployment_id = matches[0].get("externalExecutionId", "") +if deployment_id and not re.fullmatch(r"d-[A-Za-z0-9]+", deployment_id): + raise SystemExit("ERROR: invalid CodeDeploy deployment ID in pipeline state") +print(deployment_id) +') if [ -z "$DEPLOYMENT_ID" ] || [ "$DEPLOYMENT_ID" = 'None' ]; then if [ "$status" = 'Succeeded' ]; then log 'ERROR: successful pipeline has no CodeDeploy deployment'; exit 1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8d9c64982b..41aef55d921 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,10 +141,9 @@ jobs: environment: dev secrets: inherit - # Dev: build all 3 images for ECR only (no GHCR, no ARM64) + # Dev: build immutable images alongside the schema push and Trigger upload. build-dev: name: Build Dev ECR - needs: [detect-version, migrate-dev] if: github.event_name == 'push' && github.ref == 'refs/heads/dev' runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.bs_runner || matrix.gh_runner }} timeout-minutes: 30 @@ -221,44 +220,12 @@ jobs: provider: ${{ vars.CI_PROVIDER }} file: ${{ matrix.dockerfile }} platforms: linux/amd64 - tags: ${{ steps.login-ecr.outputs.registry }}/${{ steps.ecr-repo.outputs.name }}:${{ matrix.ecr_repo_secret == 'ECR_APP' && format('{0}-dev', github.sha) || 'dev' }} + tags: ${{ steps.login-ecr.outputs.registry }}/${{ steps.ecr-repo.outputs.name }}:${{ github.sha }}-dev max-cache-size-mb: ${{ matrix.cache_mb }} - - name: Promote dev app image - id: appdeploy - if: matrix.ecr_repo_secret == 'ECR_APP' - env: - REGISTRY: ${{ steps.login-ecr.outputs.registry }} - REPOSITORY: ${{ steps.ecr-repo.outputs.name }} - run: bash .github/scripts/promote-app-image.sh "$REGISTRY" "$REPOSITORY" "${GITHUB_SHA}-dev" dev - - - name: Publish dev cutover metadata - if: matrix.ecr_repo_secret == 'ECR_APP' - env: - DIGEST: ${{ steps.appdeploy.outputs.app_image_digest }} - EPOCH: ${{ steps.appdeploy.outputs.retag_epoch }} - CHANGED: ${{ steps.appdeploy.outputs.app_image_changed }} - run: | - mkdir -p dev-meta - echo "$DIGEST" > dev-meta/digest.txt - echo "$EPOCH" > dev-meta/retag_epoch.txt - echo "$CHANGED" > dev-meta/app_image_changed.txt - - - name: Upload dev cutover metadata - if: matrix.ecr_repo_secret == 'ECR_APP' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: dev-cutover-meta - path: dev-meta/ - retention-days: 1 - - # Dev: build & upload the Trigger.dev task version WITHOUT promoting it - # (--skip-promotion) to the preview "dev-sim" branch. promote-trigger-dev flips - # it at the dev ECS traffic cutover. Gated after migrate-dev so the schema is - # pushed before the new task version can run against the dev DB. + # Upload without promotion in parallel; the release gate waits for the schema. deploy-trigger-dev: name: Deploy Trigger.dev (Dev) - needs: [migrate-dev] if: github.event_name == 'push' && github.ref == 'refs/heads/dev' runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 15 @@ -310,22 +277,15 @@ jobs: exit 1 fi - # Dev: promote the skip-promoted preview version at the dev ECS traffic cutover. - # The dev app build moves :dev only after building its commit-tagged image, - # then passes the tag digest and retag timestamp through an artifact. - # trigger.dev supports promoting a specific preview branch: promote --env preview - # --branch dev-sim. + # Promote only after the gated app tag move, then observe live traffic cutover. promote-trigger-dev: name: Promote Trigger.dev (Dev) - needs: [build-dev, deploy-trigger-dev] - # Run as long as the task upload succeeded, even if a NON-app build-dev leg - # (realtime/pii/migrations) failed: the app leg pushes :dev independently and - # may have already triggered the ECS deploy, so an unrelated image failure must - # not strand the app on the old task version. The app-metadata artifact (only - # the app leg uploads it) is the real signal that an app deploy happened. + needs: [promote-images, deploy-trigger-dev] if: >- !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/dev' && + needs.promote-images.result == 'success' && + needs.promote-images.outputs.promoted == 'true' && needs.deploy-trigger-dev.result == 'success' runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} # Dev bake is 5 min and dev deploys don't queue behind a bake (serialized by the @@ -359,12 +319,6 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile --ignore-scripts - - name: Download dev cutover metadata - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: dev-cutover-meta - path: dev-meta - - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6 with: @@ -375,11 +329,11 @@ jobs: - name: Wait for ECS traffic cutover env: OVERALL_TIMEOUT: "1200" + CHANGED: ${{ needs.promote-images.outputs.app_image_changed }} + DIGEST: ${{ needs.promote-images.outputs.app_image_digest }} + EPOCH: ${{ needs.promote-images.outputs.retag_epoch }} run: | set -eo pipefail - CHANGED=$(cat dev-meta/app_image_changed.txt) - DIGEST=$(cat dev-meta/digest.txt) - EPOCH=$(cat dev-meta/retag_epoch.txt) case "$CHANGED" in true) ;; false) EPOCH=0 ;; @@ -406,19 +360,11 @@ jobs: echo "Promoting Trigger.dev version $VERSION (preview / dev-sim)" bunx trigger.dev@4.5.12 promote "$VERSION" --env preview --branch dev-sim - # Main/staging: build & upload the Trigger.dev task version WITHOUT promoting it - # (--skip-promotion). New runs keep executing the OLD promoted version until - # promote-trigger flips it at the ECS traffic cutover — so the app cutting over - # never changes which task version runs until promote-trigger (which depends on - # this job) promotes the version uploaded here. Runs in parallel with the build; - # intentionally NOT gating the app deploy on it, to avoid coupling every app / - # realtime / pii / migration deploy to trigger.dev availability. + # Build and upload tasks alongside tests and images. The unpromoted version + # cannot serve new runs; promote-images waits for it and successful migrations. deploy-trigger: name: Deploy Trigger.dev - needs: [migrate] if: >- - !cancelled() && - needs.migrate.result == 'success' && github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} @@ -598,22 +544,28 @@ jobs: tags: ${{ steps.meta.outputs.tags }} max-cache-size-mb: ${{ matrix.cache_mb }} - # Promote the sha-tagged ECR images to the deploy tags once tests and - # migrations pass. Pushing the ECR latest/staging tag is what triggers + # Promote the sha-tagged ECR images once tests, migrations, and the Trigger + # upload pass. Pushing the ECR latest/staging tag is what triggers # CodePipeline, so this seconds-long manifest retag is the deploy gate — # the image builds themselves run in parallel with the tests. A single job # (not a matrix) so all four sha manifests are verified before any tag # moves; a missing image can't produce a partial mixed-version deploy. promote-images: name: Promote Images - needs: [migrate, build-amd64] + needs: [migrate, build-amd64, deploy-trigger, migrate-dev, build-dev, deploy-trigger-dev] # Explicit results: see migrate's comment. if: >- - !cancelled() && - needs.migrate.result == 'success' && - needs.build-amd64.result == 'success' && - github.event_name == 'push' && - (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') + !cancelled() && github.event_name == 'push' && + ( + ((github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') && + needs.migrate.result == 'success' && + needs.build-amd64.result == 'success' && + needs.deploy-trigger.result == 'success') || + (github.ref == 'refs/heads/dev' && + needs.migrate-dev.result == 'success' && + needs.build-dev.result == 'success' && + needs.deploy-trigger-dev.result == 'success') + ) runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 10 permissions: @@ -638,8 +590,8 @@ jobs: - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6 with: - role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }} - aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }} + role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || github.ref == 'refs/heads/dev' && secrets.DEV_AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }} + aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || github.ref == 'refs/heads/dev' && secrets.DEV_AWS_REGION || secrets.STAGING_AWS_REGION }} - name: Login to Amazon ECR id: login-ecr @@ -662,20 +614,31 @@ jobs: echo "fresh=false" >> $GITHUB_OUTPUT fi + # Fail before moving tags if the live cutover observer lacks permission. + # Requires codepipeline:GetPipelineState on the app pipeline. + - name: Verify pipeline state access + if: steps.guard.outputs.fresh == 'true' + env: + PIPELINE: sim-${{ github.ref == 'refs/heads/main' && 'production' || github.ref == 'refs/heads/dev' && 'dev' || 'staging' }}-us-east-1-app-deployment + run: aws codepipeline get-pipeline-state --name "$PIPELINE" --query pipelineName --output text > /dev/null + - name: Promote images to deploy tags id: promote if: steps.guard.outputs.fresh == 'true' env: + SOURCE_TAG: ${{ github.ref == 'refs/heads/dev' && format('{0}-dev', github.sha) || github.sha }} ECR_REPOS: >- - ${{ secrets.ECR_APP }} ${{ secrets.ECR_MIGRATIONS }} ${{ secrets.ECR_REALTIME }} ${{ secrets.ECR_PII }} + ${{ secrets.ECR_APP }} run: | REGISTRY="${{ steps.login-ecr.outputs.registry }}" if [ "${{ github.ref }}" = "refs/heads/main" ]; then ECR_TAG="latest" + elif [ "${{ github.ref }}" = "refs/heads/dev" ]; then + ECR_TAG="dev" else ECR_TAG="staging" fi @@ -685,18 +648,20 @@ jobs: # Verify every sha image exists before moving any deploy tag, so a # missing/expired image aborts the whole promotion up front. for repo in $ECR_REPOS; do - echo "🔍 Verifying ${repo}:${{ github.sha }}" - docker buildx imagetools inspect "${REGISTRY}/${repo}:${{ github.sha }}" > /dev/null + echo "🔍 Verifying ${repo}:${SOURCE_TAG}" + docker buildx imagetools inspect "${REGISTRY}/${repo}:${SOURCE_TAG}" > /dev/null done + # Move the app last so a preceding tag failure cannot start app rollout + # and then skip the downstream Trigger promotion job. for repo in $ECR_REPOS; do - echo "🚀 Promoting ${repo}:${{ github.sha }} to ${ECR_TAG}" + echo "🚀 Promoting ${repo}:${SOURCE_TAG} to ${ECR_TAG}" if [ "$repo" = "$APP_REPO" ]; then - bash .github/scripts/promote-app-image.sh "$REGISTRY" "$APP_REPO" "$GITHUB_SHA" "$ECR_TAG" + bash .github/scripts/promote-app-image.sh "$REGISTRY" "$APP_REPO" "$SOURCE_TAG" "$ECR_TAG" else docker buildx imagetools create \ -t "${REGISTRY}/${repo}:${ECR_TAG}" \ - "${REGISTRY}/${repo}:${{ github.sha }}" + "${REGISTRY}/${repo}:${SOURCE_TAG}" fi done From 1d086645f8a071e875095d0b536b921237d4944a Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 16 Sep 2026 17:59:51 -0700 Subject: [PATCH 34/43] fix(knowledge): bound KB block vector retrieval (#7903) * fix(knowledge): bound KB block vector retrieval * fix(knowledge): align search fixtures with scoped probing --- .github/workflows/test-build.yml | 1 + .../app/api/knowledge/search/utils.test.ts | 64 +++-- .../kb-block-search.integration.ts | 135 ++++++++++ apps/sim/lib/knowledge/search/queries.test.ts | 235 ++++++++++++++++-- apps/sim/lib/knowledge/search/queries.ts | 117 ++++++--- 5 files changed, 461 insertions(+), 91 deletions(-) create mode 100644 apps/sim/lib/knowledge/__integration__/kb-block-search.integration.ts diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index bb504e22fe2..c2ac8153946 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -221,6 +221,7 @@ jobs: lib/knowledge/__integration__/search-source-progress.integration.ts lib/knowledge/__integration__/search-source-pagination.integration.ts lib/knowledge/__integration__/search-reference-batching.integration.ts + lib/knowledge/__integration__/kb-block-search.integration.ts lib/core/outbox/service.integration.ts lib/knowledge/__integration__/connector-upload.integration.ts lib/uploads/contexts/organization-logo/application.integration.ts diff --git a/apps/sim/app/api/knowledge/search/utils.test.ts b/apps/sim/app/api/knowledge/search/utils.test.ts index 2c0a4abaf59..fc19016b170 100644 --- a/apps/sim/app/api/knowledge/search/utils.test.ts +++ b/apps/sim/app/api/knowledge/search/utils.test.ts @@ -212,6 +212,10 @@ describe('Knowledge Search Utils', () => { describe('handleTagAndVectorSearch', () => { it('returns only bounded ranked rows without first materializing every matching tag ID', async () => { resetDbChainMock() + queueTableRows( + schemaMock.embedding, + Array.from({ length: 201 }, (_, index) => ({ id: `candidate-${index}` })) + ) queueTableRows(schemaMock.embedding, [makeResult('second', 0.2), makeResult('first', 0.1)]) const results = await handleTagAndVectorSearch({ @@ -226,9 +230,11 @@ describe('Knowledge Search Utils', () => { }) expect(results.map((row) => row.id)).toEqual(['first', 'second']) - expect(dbChainMockFns.select).toHaveBeenCalledTimes(2) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(3) expect(dbChainMockFns.as).toHaveBeenCalledWith('ranked_embeddings') - expect(dbChainMockFns.select.mock.calls[0][0]).toHaveProperty('distance') + expect(Object.keys(dbChainMockFns.select.mock.calls[0][0])).toEqual(['id']) + expect(dbChainMockFns.limit).toHaveBeenNthCalledWith(1, 201) + expect(dbChainMockFns.select.mock.calls[1][0]).toHaveProperty('distance') expect(dbChainMockFns.limit).toHaveBeenCalledWith(2) }) @@ -536,6 +542,7 @@ describe('Knowledge Search Utils', () => { }) it('runs a single retrieval leg in vector mode', async () => { + queueTableRows(schemaMock.embedding, [{ id: 'vector-hit' }]) queueTableRows(schemaMock.embedding, [makeResult('vector-hit')]) const results = await executeKnowledgeSearch({ @@ -548,20 +555,19 @@ describe('Knowledge Search Utils', () => { }) expect(results.map((r) => r.id)).toEqual(['vector-hit']) - expect(dbChainMockFns.select).toHaveBeenCalledTimes(2) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(3) expect(dbChainMockFns.as).toHaveBeenCalledWith('ranked_embeddings') }) it('runs both legs and fuses them in hybrid mode', async () => { /** - * Chains dequeue in creation order. Hybrid legs over-fetch past the - * plain scan's candidate pool, so the vector leg opens its transaction - * and applies the scan settings before selecting: the keyword ranking - * pass is built first, then the vector select, then hydration. + * Chains dequeue in creation order: keyword ranking, the budgeted vector + * probe, keyword hydration, then vector ranking and hydration in one query. */ queueTableRows(schemaMock.embedding, [{ id: 'keyword-hit', keywordRank: 0.9 }]) - queueTableRows(schemaMock.embedding, [makeResult('vector-hit')]) + queueTableRows(schemaMock.embedding, [{ id: 'vector-hit' }]) queueTableRows(schemaMock.embedding, [makeResult('keyword-hit')]) + queueTableRows(schemaMock.embedding, [makeResult('vector-hit')]) const results = await executeKnowledgeSearch({ knowledgeBaseIds: ['kb-123'], @@ -573,39 +579,31 @@ describe('Knowledge Search Utils', () => { }) expect(results.map((r) => r.id).sort()).toEqual(['keyword-hit', 'vector-hit']) - expect(dbChainMockFns.select).toHaveBeenCalledTimes(4) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(5) }) - it('falls back to vector results when the keyword leg fails', async () => { + it('propagates unexpected keyword errors after the vector leg finishes', async () => { /** The failing ranking chain is still built first and takes the first queued set. */ queueTableRows(schemaMock.embedding, [{ id: 'never-ranked', keywordRank: 0 }]) + queueTableRows(schemaMock.embedding, [{ id: 'vector-hit' }]) queueTableRows(schemaMock.embedding, [makeResult('vector-hit')]) - /** - * Both legs share one `orderBy` spy, so target the keyword leg by its - * ranking expression. Calling the untouched spy first captures the - * sentinel that tells the mock to build its normal chain, which the - * vector leg still needs. - */ - const chainDefault = dbChainMockFns.orderBy() - dbChainMockFns.orderBy.mockImplementation((fragment: unknown) => { - const text = (fragment as { strings?: string[] })?.strings?.join('') ?? '' - if (text.includes('ts_rank_cd')) { - throw new Error('tsquery blew up') - } - return chainDefault - }) - - const results = await executeKnowledgeSearch({ - knowledgeBaseIds: ['kb-123'], - access: WORKSPACE_ACCESS_SCOPE, - topK: 10, - searchMode: 'hybrid', - query: 'PROJ-1234', - queryVector: JSON.stringify([0.1, 0.2, 0.3]), + const failure = new Error('tsquery failed') + dbChainMockFns.orderBy.mockImplementationOnce(() => { + throw failure }) - expect(results.map((r) => r.id)).toEqual(['vector-hit']) + await expect( + executeKnowledgeSearch({ + knowledgeBaseIds: ['kb-123'], + access: WORKSPACE_ACCESS_SCOPE, + topK: 10, + searchMode: 'hybrid', + query: 'PROJ-1234', + queryVector: JSON.stringify([0.1, 0.2, 0.3]), + }) + ).rejects.toBe(failure) + expect(dbChainMockFns.as).toHaveBeenCalledWith('ranked_embeddings') }) it('skips both query legs when only tag filters are provided', async () => { diff --git a/apps/sim/lib/knowledge/__integration__/kb-block-search.integration.ts b/apps/sim/lib/knowledge/__integration__/kb-block-search.integration.ts new file mode 100644 index 00000000000..dba85e74b3d --- /dev/null +++ b/apps/sim/lib/knowledge/__integration__/kb-block-search.integration.ts @@ -0,0 +1,135 @@ +/** KB block retrieval against disposable PostgreSQL, using a workspace API-key identity. */ +import type { Principal } from '@sim/auth/principal' +import { db } from '@sim/db' +import { document, embedding, knowledgeBase, organization, user, workspace } from '@sim/db/schema' +import { generateId } from '@sim/utils/id' +import { eq, inArray } from 'drizzle-orm' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { + createKnowledgeAclFixtureIds, + seedKnowledgeAclFixture, +} from '@/lib/knowledge/__integration__/seed-source-access-fixture' +import { createKnowledgeAccessProvider } from '@/lib/knowledge/access/scope' +import { retrieveKnowledgeSearch } from '@/lib/knowledge/search/queries' +import { embeddingVectorValues } from '@/lib/knowledge/vector-columns' + +describe('API-key KB block fan-out', () => { + const ids = createKnowledgeAclFixtureIds() + const bases = Array.from({ length: 18 }, () => ({ + id: generateId(), + visible: generateId(), + denied: generateId(), + excluded: generateId(), + })) + const principal: Principal = { + kind: 'workspace_api_key', + workspaceId: ids.workspaceId, + keyId: 'fixture-key', + } + const vector = [1, ...Array(1535).fill(0)] + const queryVector = { + vector: JSON.stringify(vector), + dimensions: 1536 as const, + model: 'text-embedding-3-small', + } + + beforeAll(async () => { + await seedKnowledgeAclFixture(ids, { connectorType: 'google_drive' }) + await db.insert(knowledgeBase).values( + bases.map((base, index) => ({ + id: base.id, + userId: ids.aliceId, + workspaceId: ids.workspaceId, + name: `KB block ${index}`, + })) + ) + await db.insert(document).values( + bases.flatMap((base) => + (['visible', 'denied', 'excluded'] as const).map((kind) => ({ + id: base[kind], + knowledgeBaseId: base.id, + filename: kind, + fileUrl: `https://fixture.invalid/${base[kind]}`, + fileSize: 12, + mimeType: 'text/plain', + processingStatus: 'completed', + acl: kind === 'denied' ? [`u:${ids.aliceId}@fixture.test`] : ['ws'], + userExcluded: kind === 'excluded', + })) + ) + ) + await db.insert(embedding).values( + bases.flatMap((base) => + (['visible', 'denied', 'excluded'] as const).map((kind) => ({ + id: generateId(), + documentId: base[kind], + knowledgeBaseId: base.id, + chunkIndex: 0, + chunkHash: base[kind], + content: `Fixture policy ${kind}`, + contentLength: 24, + tokenCount: 5, + startOffset: 0, + endOffset: 24, + tag1: 'policy', + ...embeddingVectorValues(1536, vector), + })) + ) + ) + }) + + afterAll(async () => { + await db.delete(workspace).where(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() + }) + + it.each([false, true])( + 'completes 18 concurrent KB searches with access checks intact (tag filter: %s)', + async (withTags) => { + const previousDebug = db.$client.options.debug + const statements: string[] = [] + db.$client.options.debug = (_connection, query) => { + if (statements.length < 250) statements.push(query) + } + try { + const results = await Promise.all( + bases.map(async (base) => { + const accessProvider = createKnowledgeAccessProvider(principal, { + workspaceId: ids.workspaceId, + knowledgeBaseIds: [base.id], + }) + const access = await accessProvider.get() + expect(access.kind).toBe('workspace') + return retrieveKnowledgeSearch({ + knowledgeBaseIds: [base.id], + topK: 2, + access, + accessProvider, + searchMode: 'vector', + query: 'Find the fixture policy', + queryVector, + ...(withTags && { + structuredFilters: [ + { tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'policy' }, + ], + }), + }) + }) + ) + for (const [index, result] of results.entries()) { + expect(result.retrieval).toEqual({ status: 'complete', timedOutLegs: [] }) + expect(result.rows.map((row) => row.documentId)).toEqual([bases[index].visible]) + expect(result.rows[0].knowledgeBaseId).toBe(bases[index].id) + expect(result.rows[0].distance).toBeCloseTo(0) + } + expect(statements.filter((query) => query.includes('statement_timeout'))).toHaveLength(36) + expect(statements.filter((query) => query.includes('+ 0'))).toHaveLength(18) + expect(statements.some((query) => query.includes('hnsw.iterative_scan'))).toBe(false) + } finally { + db.$client.options.debug = previousDebug + } + } + ) +}) diff --git a/apps/sim/lib/knowledge/search/queries.test.ts b/apps/sim/lib/knowledge/search/queries.test.ts index a5f20dc7f20..b7a5b9ebabe 100644 --- a/apps/sim/lib/knowledge/search/queries.test.ts +++ b/apps/sim/lib/knowledge/search/queries.test.ts @@ -1,6 +1,7 @@ /** * @vitest-environment node */ +import { db } from '@sim/db' import { dbChainMockFns, hasMockCondition, @@ -15,7 +16,12 @@ import { WORKSPACE_ACCESS_TOKENS, } from '@/lib/knowledge/access/types' import { buildTagFilterCondition } from '@/lib/knowledge/documents/tag-filter' -import { SearchBudget } from '@/lib/knowledge/search/budget' +import { + SearchBudget, + SearchDeadlineError, + type SearchExecutor, +} from '@/lib/knowledge/search/budget' +import type { SearchStage } from '@/lib/knowledge/search/diagnostics' import { executeKeywordSearch, getStructuredTagFilters, @@ -310,7 +316,174 @@ describe('getStructuredTagFilters', () => { }) }) +describe('KB block vector retrieval', () => { + const params: SearchParams = { + knowledgeBaseIds: ['kb-small'], + topK: 2, + access: { kind: 'workspace', tokens: WORKSPACE_ACCESS_TOKENS }, + queryVector: { vector: '[0.1,0.2]', dimensions: 1536, model: 'text-embedding-3-small' }, + distanceThreshold: 1, + } + + beforeEach(() => resetDbChainMock()) + afterEach(() => { + vi.restoreAllMocks() + vi.useRealTimers() + }) + + it.each([handleVectorOnlySearch, handleTagAndVectorSearch])( + 'does not acquire a connection or start SQL after the KB retrieval deadline', + async (search) => { + const budget = new SearchBudget('vector', performance.now() - 1) + expect( + await search({ + ...params, + budget, + structuredFilters: [ + { tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'release' }, + ], + }) + ).toEqual([]) + expect(budget.timedOut).toBe(true) + expect(dbChainMockFns.transaction).not.toHaveBeenCalled() + expect(dbChainMockFns.select).not.toHaveBeenCalled() + } + ) + + it('ranks all candidates in a small KB exactly instead of traversing the shared vector index', async () => { + queueTableRows(schemaMock.embedding, [{ id: 'near' }, { id: 'far' }]) + queueTableRows(schemaMock.embedding, [ + { id: 'far', distance: 0.2 }, + { id: 'near', distance: 0.1 }, + ]) + const rows = await handleVectorOnlySearch(params) + expect(rows.map((row) => row.id)).toEqual(['near', 'far']) + expect(dbChainMockFns.execute).not.toHaveBeenCalled() + expect(Object.keys(dbChainMockFns.select.mock.calls[0][0])).toEqual(['id']) + expect(dbChainMockFns.limit).toHaveBeenNthCalledWith(1, 201) + expect(render(dbChainMockFns.orderBy.mock.calls[0][0]).sql).toContain('+ 0') + expect( + hasMockCondition( + dbChainMockFns.where.mock.calls[1][0], + (node) => + node.type === 'inArray' && + node.column === schemaMock.embedding.id && + JSON.stringify(node.values) === JSON.stringify(['near', 'far']) + ) + ).toBe(true) + }) + + it.each([1, 200, 201])( + 'reports a %i-candidate SQL timeout as partial, not an empty complete result', + async (count) => { + queueTableRows( + schemaMock.embedding, + Array.from({ length: count }, (_, index) => ({ id: `candidate-${index}` })) + ) + dbChainMockFns.orderBy + .mockImplementationOnce(dbChainMockFns.orderBy.getMockImplementation()!) + .mockRejectedValueOnce(new Error('Statement canceled', { cause: { code: '57014' } })) + const result = await retrieveKnowledgeSearch({ + ...params, + query: 'fixture policy', + searchMode: 'vector', + }) + expect(result).toEqual({ + rows: [], + retrieval: { status: 'partial', timedOutLegs: ['vector'] }, + }) + const usesAnn = dbChainMockFns.execute.mock.calls.some(([statement]) => + render(statement).sql.includes('hnsw.iterative_scan') + ) + expect(usesAnn).toBe(count > 200) + } + ) + + it('does not convert an unexpected ranking error into partial retrieval', async () => { + queueTableRows(schemaMock.embedding, [{ id: 'candidate' }]) + const failure = new Error('Connection lost', { cause: { code: '08006' } }) + dbChainMockFns.orderBy + .mockImplementationOnce(dbChainMockFns.orderBy.getMockImplementation()!) + .mockRejectedValueOnce(failure) + await expect( + retrieveKnowledgeSearch({ ...params, query: 'fixture policy', searchMode: 'vector' }) + ).rejects.toBe(failure) + }) + + it('reports incomplete retrieval for 18 expired pool waiters without starting their SQL later', async () => { + vi.useFakeTimers() + const release: Array<() => void> = [] + const transactions: Array> = [] + vi.spyOn(db, 'transaction').mockImplementation((callback) => { + const transaction = new Promise((resolve) => release.push(resolve)).then(() => + callback(db as never) + ) + transactions.push(transaction) + return transaction as ReturnType + }) + const pending = Promise.all( + Array.from({ length: 18 }, (_, index) => + retrieveKnowledgeSearch({ + ...params, + knowledgeBaseIds: [`kb-${index}`], + query: 'fixture policy', + searchMode: 'vector', + vectorBudgetMs: 50, + }) + ) + ) + await vi.advanceTimersByTimeAsync(60) + const results = await pending + expect(results).toHaveLength(18) + for (const result of results) { + expect(result).toEqual({ + rows: [], + retrieval: { status: 'partial', timedOutLegs: ['vector'] }, + }) + } + for (const resume of release) resume() + const settled = await Promise.allSettled(transactions) + expect(settled).toHaveLength(18) + for (const transaction of settled) { + expect(transaction.status).toBe('rejected') + if (transaction.status === 'rejected') + expect(transaction.reason).toBeInstanceOf(SearchDeadlineError) + } + expect(dbChainMockFns.select).not.toHaveBeenCalled() + expect(dbChainMockFns.execute).not.toHaveBeenCalled() + }) + + it.each([1, 201])( + 'shares the remaining SQL budget between the probe and %i-candidate ranking', + async (count) => { + vi.spyOn(performance, 'now').mockReturnValue(0) + queueTableRows( + schemaMock.embedding, + Array.from({ length: count }, (_, index) => ({ id: `candidate-${index}` })) + ) + const query = SearchBudget.prototype.query + vi.spyOn(SearchBudget.prototype, 'query').mockImplementation(async function ( + this: SearchBudget, + stage: SearchStage, + run: (executor: SearchExecutor) => PromiseLike + ) { + const runQuery: SearchBudget['query'] = query.bind(this) + const result = await runQuery(stage, run) + if (stage === 'vector.probe') vi.spyOn(performance, 'now').mockReturnValue(30) + return result + }) + await handleVectorOnlySearch({ ...params, budget: new SearchBudget('vector', 100) }) + const timeouts = dbChainMockFns.execute.mock.calls + .map(([statement]) => render(statement)) + .filter((statement) => statement.sql.includes('statement_timeout')) + .map((statement) => statement.params[0]) + expect(timeouts).toEqual(count === 1 ? ['100', '70'] : ['100', '70', '70']) + } + ) +}) + describe('vector scan settings', () => { + const largeProbe = Array.from({ length: 201 }, (_, index) => ({ id: `probe-${index}` })) const params: SearchParams = { knowledgeBaseIds: ['kb-small'], topK: 2, @@ -321,13 +494,14 @@ describe('vector scan settings', () => { beforeEach(() => { resetDbChainMock() + queueTableRows(schemaMock.embedding, largeProbe) }) afterEach(() => { vi.useRealTimers() }) - it('tunes a small workspace search before querying its KB scope, preserving distance ordering', async () => { + it('tunes an overflowing KB scope without limiting ANN to the probe prefix', async () => { queueTableRows(schemaMock.embedding, [ { id: 'far', distance: 0.2 }, { id: 'near', distance: 0.1 }, @@ -341,11 +515,11 @@ describe('vector scan settings', () => { params: ['20000'], }) expect(dbChainMockFns.execute.mock.invocationCallOrder[0]).toBeLessThan( - dbChainMockFns.select.mock.invocationCallOrder[0] + dbChainMockFns.select.mock.invocationCallOrder[1] ) expect( hasMockCondition( - dbChainMockFns.where.mock.calls[0][0], + dbChainMockFns.where.mock.calls[1][0], (node) => node.type === 'inArray' && node.column === schemaMock.embedding.knowledgeBaseId && @@ -354,10 +528,16 @@ describe('vector scan settings', () => { ) ).toBe(true) expect(dbChainMockFns.limit).toHaveBeenCalledWith(2) - expect(dbChainMockFns.limit).toHaveBeenCalledOnce() - expect(Object.keys(dbChainMockFns.select.mock.calls[0][0])).toEqual(['id', 'distance']) - const ranked = dbChainMockFns.from.mock.calls[1][0] - expect(dbChainMockFns.select.mock.calls[1][0].distance).toBe(ranked.distance) + expect(dbChainMockFns.limit).toHaveBeenCalledTimes(2) + expect( + hasMockCondition( + dbChainMockFns.where.mock.calls[1][0], + (node) => node.type === 'inArray' && node.column === schemaMock.embedding.id + ) + ).toBe(false) + expect(Object.keys(dbChainMockFns.select.mock.calls[1][0])).toEqual(['id', 'distance']) + const ranked = dbChainMockFns.from.mock.calls[2][0] + expect(dbChainMockFns.select.mock.calls[2][0].distance).toBe(ranked.distance) expect(dbChainMockFns.innerJoin).toHaveBeenCalledWith( schemaMock.embedding, expect.objectContaining({ @@ -367,20 +547,22 @@ describe('vector scan settings', () => { }) ) expect(dbChainMockFns.orderBy).toHaveBeenLastCalledWith(ranked.distance) - expect(dbChainMockFns.limit.mock.invocationCallOrder[0]).toBeLessThan( - dbChainMockFns.select.mock.invocationCallOrder[1] + expect(dbChainMockFns.limit.mock.invocationCallOrder[1]).toBeLessThan( + dbChainMockFns.select.mock.invocationCallOrder[2] ) }) - it('shares one local configuration across all KB vector legs and trims their sorted merge', async () => { + it('tunes each KB leg and trims their sorted merge', async () => { const knowledgeBaseIds = ['kb-1', 'kb-2', 'kb-3', 'kb-4', 'kb-5'] - for (let index = 0; index < knowledgeBaseIds.length; index++) + for (let index = 0; index < knowledgeBaseIds.length; index++) { + if (index > 0) queueTableRows(schemaMock.embedding, largeProbe) queueTableRows(schemaMock.embedding, [{ id: `row-${index}`, distance: (5 - index) / 10 }]) + } const rows = await handleVectorOnlySearch({ ...params, knowledgeBaseIds }) expect(rows.map((row) => row.id)).toEqual(['row-4', 'row-3']) - expect(dbChainMockFns.transaction).toHaveBeenCalledOnce() - expect(dbChainMockFns.execute).toHaveBeenCalledOnce() - expect(dbChainMockFns.select).toHaveBeenCalledTimes(10) + expect(dbChainMockFns.transaction).toHaveBeenCalledTimes(5) + expect(dbChainMockFns.execute).toHaveBeenCalledTimes(5) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(15) for (const kbId of knowledgeBaseIds) expect( dbChainMockFns.where.mock.calls.some(([condition]) => @@ -432,20 +614,23 @@ describe('vector scan settings', () => { queueTableRows(schemaMock.embedding, [{ id: 'fallback', distance: 0.1 }]) expect((await handleVectorOnlySearch(params)).map((row) => row.id)).toEqual(['fallback']) expect(dbChainMockFns.execute).toHaveBeenCalledOnce() - expect(dbChainMockFns.select).toHaveBeenCalledTimes(2) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(3) await handleVectorOnlySearch(params) expect(dbChainMockFns.transaction).toHaveBeenCalledOnce() await vi.advanceTimersByTimeAsync(10 * 60 * 1000 + 1) + queueTableRows(schemaMock.embedding, largeProbe) await handleVectorOnlySearch(params) expect(dbChainMockFns.transaction).toHaveBeenCalledTimes(2) expect(dbChainMockFns.execute).toHaveBeenCalledTimes(2) }) - it('propagates an unrelated settings failure without issuing a query or disabling later tuning', async () => { + it('propagates an unrelated settings failure without ranking or disabling later tuning', async () => { const failure = { code: '08006', message: 'Connection lost' } dbChainMockFns.execute.mockRejectedValueOnce(failure) await expect(handleVectorOnlySearch(params)).rejects.toBe(failure) - expect(dbChainMockFns.select).not.toHaveBeenCalled() + expect(dbChainMockFns.select).toHaveBeenCalledOnce() + expect(dbChainMockFns.orderBy).not.toHaveBeenCalled() + queueTableRows(schemaMock.embedding, largeProbe) await handleVectorOnlySearch(params) expect(dbChainMockFns.execute).toHaveBeenCalledTimes(2) }) @@ -456,7 +641,8 @@ describe('vector scan settings', () => { .mockImplementationOnce(dbChainMockFns.orderBy.getMockImplementation()!) .mockRejectedValueOnce(failure) await expect(handleVectorOnlySearch(params)).rejects.toBe(failure) - expect(dbChainMockFns.select).toHaveBeenCalledTimes(2) + expect(dbChainMockFns.select).toHaveBeenCalledTimes(3) + queueTableRows(schemaMock.embedding, largeProbe) await handleVectorOnlySearch(params) expect(dbChainMockFns.execute).toHaveBeenCalledTimes(2) }) @@ -477,9 +663,10 @@ describe('workspace search filters before ranking', () => { } beforeEach(() => resetDbChainMock()) - function expectScopeOnEveryQuery() { - expect(dbChainMockFns.where).toHaveBeenCalled() - for (const [condition] of dbChainMockFns.where.mock.calls) { + function expectScopeOnEveryQuery(skipIdentityProbe = false) { + const queries = dbChainMockFns.where.mock.calls.slice(skipIdentityProbe ? 1 : 0) + expect(queries.length).toBeGreaterThan(0) + for (const [condition] of queries) { expect( hasMockCondition( condition, @@ -512,13 +699,15 @@ describe('workspace search filters before ranking', () => { it.each([handleVectorOnlySearch, handleTagOnlySearch, handleTagAndVectorSearch])( 'applies the full document scope to vector and tag searches', async (search) => { + const hasIdentityProbe = search !== handleTagOnlySearch + if (hasIdentityProbe) queueTableRows(schemaMock.embedding, [{ id: 'candidate' }]) await search({ ...params, structuredFilters: [ { tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'launch' }, ], }) - expectScopeOnEveryQuery() + expectScopeOnEveryQuery(hasIdentityProbe) } ) diff --git a/apps/sim/lib/knowledge/search/queries.ts b/apps/sim/lib/knowledge/search/queries.ts index 84ace644039..e67a126fa5e 100644 --- a/apps/sim/lib/knowledge/search/queries.ts +++ b/apps/sim/lib/knowledge/search/queries.ts @@ -56,6 +56,7 @@ const CANDIDATE_HNSW_SCAN_MEM_MULTIPLIER = '2' const MIN_VECTOR_RERANK_CANDIDATES = 400 const MAX_VECTOR_RERANK_CANDIDATES = 1600 const VECTOR_RERANK_OVERSAMPLING = 8 +const MAX_EXACT_KB_VECTOR_CANDIDATES = 200 /** How long to stop trying the iterative-scan settings after the server rejected them. */ const HNSW_SETTINGS_UNSUPPORTED_RETRY_MS = 10 * 60 * 1000 @@ -777,40 +778,91 @@ export async function handleVectorOnlySearch(params: SearchParams): Promise - selectRankedVectorResults( - executor, - distance, - [ - kbScope, - ...getVisibilityConditions(access, params.filters), - sql`${distance} < ${distanceThreshold}`, - ], - limit - ) - /** * A relaxed-order iterative scan may hand rows back slightly out of distance * order, so both paths re-sort in memory before trimming to `topK`. */ if (strategy.useParallel) { const parallelLimit = Math.ceil(topK / knowledgeBaseIds.length) + 5 - const allResults = await withVectorScanSettings(async (executor) => { - const parallelResults = await Promise.all( - knowledgeBaseIds.map((kbId) => - vectorLeg(executor, eq(embedding.knowledgeBaseId, kbId), parallelLimit) - ) + const allResults: SearchResult[] = [] + /** Keep one active KB leg per request so multi-base searches cannot monopolize the pool. */ + for (const kbId of knowledgeBaseIds) { + allResults.push( + ...(await selectScopedVectorResults( + params, + distance, + eq(embedding.knowledgeBaseId, kbId), + parallelLimit + )) ) - return parallelResults.flat() - }) + if (params.budget?.timedOut) break + } return allResults.sort((a, b) => a.distance - b.distance).slice(0, topK) } - const rows = await withVectorScanSettings((executor) => - vectorLeg(executor, inArray(embedding.knowledgeBaseId, knowledgeBaseIds), topK) + const rows = await selectScopedVectorResults( + params, + distance, + inArray(embedding.knowledgeBaseId, knowledgeBaseIds), + topK ) return rows.sort((a, b) => a.distance - b.distance) } +/** + * KB runs without a human subject still need bounded small-scope ranking. Probe only chunk + * identities, then reapply every access and visibility predicate before ranking and hydration. + * An overflowing probe selects ANN over the whole scope, never a truncated candidate prefix. + */ +async function selectScopedVectorResults( + params: SearchParams, + distance: SQL, + kbScope: SQL | undefined, + limit: number, + tagConditions: (SQL | undefined)[] = [] +): Promise { + try { + const probe = await runSearchQuery(params.budget, 'vector.probe', (executor) => + executor + .select({ id: embedding.id }) + .from(embedding) + .where(and(kbScope, eq(embedding.enabled, true), ...tagConditions)) + .limit(MAX_EXACT_KB_VECTOR_CANDIDATES + 1) + ) + if (probe.length === 0) return [] + const conditions = [ + kbScope, + ...getVisibilityConditions(params.access, params.filters), + ...tagConditions, + sql`${distance} < ${params.distanceThreshold}`, + ] + if (probe.length <= MAX_EXACT_KB_VECTOR_CANDIDATES) { + annotateSearchDiagnostics({ vectorRanking: 'exact' }) + return await runSearchQuery(params.budget, 'vector.exact', (executor) => + selectRankedVectorResults( + executor, + distance, + [ + ...conditions, + inArray( + embedding.id, + probe.map((candidate) => candidate.id) + ), + ], + limit, + true + ) + ) + } + return await withVectorScanSettings( + (executor) => selectRankedVectorResults(executor, distance, conditions, limit), + params.budget + ) + } catch (error) { + if (!params.budget?.isTimeout(error)) throw error + return [] + } +} + /** * Bound ANN traversal and rerank a small candidate pool against the original vectors. * Nearest-neighbor traversal drives document visibility lookups, avoiding a sort of @@ -1023,14 +1075,15 @@ function selectRankedVectorResults( executor: SearchExecutor, distance: SQL, conditions: (SQL | undefined)[], - limit: number + limit: number, + exact = false ) { const ranked = executor .select({ id: embedding.id, distance: distance.as('distance') }) .from(embedding) .innerJoin(document, eq(embedding.documentId, document.id)) .where(and(...conditions)) - .orderBy(distance) + .orderBy(exact ? sql`(${distance}) + 0` : distance) .limit(limit) .as('ranked_embeddings') @@ -1317,18 +1370,12 @@ export async function handleTagAndVectorSearch(params: SearchParams): Promise - selectRankedVectorResults( - executor, - distance, - [ - inArray(embedding.knowledgeBaseId, knowledgeBaseIds), - ...getVisibilityConditions(access, params.filters), - ...tagFilterConditions, - sql`${distance} < ${distanceThreshold}`, - ], - topK - ) + const rows = await selectScopedVectorResults( + params, + distance, + inArray(embedding.knowledgeBaseId, knowledgeBaseIds), + topK, + tagFilterConditions ) return rows.sort((a, b) => a.distance - b.distance) } From 6dffde3ef66dc3503e14e90353d72a6071401554 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 16 Sep 2026 18:18:21 -0700 Subject: [PATCH 35/43] chore(ci): simplify Trigger release workflow (#7905) * chore(ci): unify Trigger preparation and promotion jobs * chore(ci): remove Trigger deployment test harness --- .github/scripts/test-trigger-deploy.py | 413 ------------------------- .github/workflows/ci.yml | 219 +++---------- .github/workflows/test-build.yml | 3 - 3 files changed, 51 insertions(+), 584 deletions(-) delete mode 100644 .github/scripts/test-trigger-deploy.py diff --git a/.github/scripts/test-trigger-deploy.py b/.github/scripts/test-trigger-deploy.py deleted file mode 100644 index 64febf4b611..00000000000 --- a/.github/scripts/test-trigger-deploy.py +++ /dev/null @@ -1,413 +0,0 @@ -"""Exercise the deployment gates with scripted AWS responses; no live mutations.""" -import json -import os -from pathlib import Path -import re -import subprocess -import tempfile -import unittest - -SCRIPTS = Path(__file__).resolve().parent -DIGEST = 'sha256:' + 'a' * 64 -OTHER_DIGEST = 'sha256:' + 'b' * 64 - - -def execution(start=1000, digest=DIGEST, identifier='execution-current'): - return { - 'startTime': start, - 'pipelineExecutionId': identifier, - 'sourceRevisions': [{'actionName': 'ECR_Source', 'revisionId': digest}], - } - - -def deploy_action(identifier='action-current', start=1000, status='InProgress'): - return {'actionName': 'Deploy_to_ECS', 'actionExecutionId': identifier, - 'startTime': start, 'status': status, 'output': {}} - - -def deploy_state(execution_id='execution-current', action_id='action-current', deployment_id='d-current'): - return {'latestExecution': {'pipelineExecutionId': execution_id}, 'actionStates': [ - {'actionName': 'Deploy_to_ECS', 'latestExecution': { - 'actionExecutionId': action_id, 'externalExecutionId': deployment_id, 'status': 'InProgress'}}]} - - -class DeploymentGateTests(unittest.TestCase): - def run_script(self, script, args, responses): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - fixture = root / 'responses.json' - fixture.write_text(json.dumps(responses)) - (root / 'aws').write_text('''#!/usr/bin/env python3 -import json, os, pathlib, sys -root = pathlib.Path(os.environ['FIXTURE_DIR']) -args = sys.argv[1:] -if args[0] == '--cli-connect-timeout': - args = args[4:] -service, operation = args[:2] -key = operation -if operation == 'get-deployment-target': - key += ':' + args[args.index('--target-id') + 1] -with (root / 'calls').open('a') as stream: - stream.write(' '.join(args) + '\\n') -responses = json.loads((root / 'responses.json').read_text()) -if key not in responses: - raise SystemExit('Unexpected AWS call: ' + key) -response = responses[key] -# Objects model steady state; lists are finite, ordered expectations. -if isinstance(response, list): - if not response: - raise SystemExit('Unexpected extra AWS call: ' + key) - next_response = response.pop(0) - (root / 'responses.json').write_text(json.dumps(responses)) - response = next_response -if response.get('error'): - sys.stderr.write(response['error']) - sys.exit(254) -if response.get('advance_clock'): - clock = root / 'clock' - value = int(clock.read_text()) if clock.exists() else 1000 - clock.write_text(str(value + response['advance_clock'])) -print(response.get('text', json.dumps(response.get('json')))) -''') - (root / 'docker').write_text('''#!/usr/bin/env python3 -import os, pathlib, sys -root = pathlib.Path(os.environ['FIXTURE_DIR']) -with (root / 'calls').open('a') as stream: - stream.write('docker ' + ' '.join(sys.argv[1:]) + '\\n') -''') - (root / 'date').write_text('''#!/usr/bin/env python3 -import os, pathlib -path = pathlib.Path(os.environ['FIXTURE_DIR']) / 'clock' -value = int(path.read_text()) if path.exists() else 1000 -path.write_text(str(value + 1)) -print(value) -''') - (root / 'sleep').write_text('#!/bin/sh\nexit 0\n') - for name in ('aws', 'date', 'sleep', 'docker'): - (root / name).chmod(0o755) - result = subprocess.run( - ['bash', str(SCRIPTS / script), *args], - env={**os.environ, 'PATH': f'{root}:{os.environ["PATH"]}', - 'FIXTURE_DIR': str(root), 'POLL_INTERVAL': '1', 'OVERALL_TIMEOUT': '12', - 'GITHUB_OUTPUT': str(root / 'outputs')}, - capture_output=True, text=True, timeout=10, - ) - calls = (root / 'calls').read_text() if (root / 'calls').exists() else '' - result.github_output = (root / 'outputs').read_text() if (root / 'outputs').exists() else '' - return result, calls - - def poll(self, updates=None, since='1000'): - responses = { - 'list-pipeline-executions': {'json': [execution()]}, - 'get-pipeline-execution': {'text': 'InProgress'}, - 'get-pipeline-state': {'json': deploy_state()}, - 'list-action-executions': {'json': [deploy_action()]}, - 'get-deployment': {'text': 'InProgress'}, - 'list-deployment-targets': {'text': 'target-one\ttarget-two'}, - 'get-deployment-target:target-one': {'text': 'Succeeded'}, - 'get-deployment-target:target-two': {'text': 'Succeeded'}, - } - responses.update(updates or {}) - return self.run_script('wait-for-ecs-cutover.sh', ['app-pipeline', DIGEST, since], responses) - - def test_waits_for_every_target(self): - targets = ('target-one', 'target-two') - for pending_target in targets: - with self.subTest(pending_target=pending_target): - result, calls = self.poll({f'get-deployment-target:{pending_target}': [ - {'text': 'InProgress'}, {'text': 'Succeeded'}]}) - self.assertEqual(result.returncode, 0, result.stderr) - for target in targets: - self.assertEqual(calls.count(f'--target-id {target}'), 2) - - def test_rejects_stale_execution_inside_former_clock_skew_window(self): - result, calls = self.poll({'list-pipeline-executions': {'json': [execution(start=999)]}}) - self.assertNotEqual(result.returncode, 0) - self.assertIn('timed out', result.stdout) - self.assertNotIn('get-pipeline-execution ', calls) - - def test_chooses_newest_matching_execution(self): - result, calls = self.poll({'list-pipeline-executions': {'json': [ - execution(1000, identifier='execution-old'), execution(1001)]}}) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn('--pipeline-execution-id execution-current', calls) - - def test_changed_image_rejects_newer_different_execution(self): - result, calls = self.poll({'list-pipeline-executions': {'json': [ - execution(), execution(1001, digest=OTHER_DIGEST, identifier='execution-newer')]}}) - self.assertNotEqual(result.returncode, 0) - self.assertIn('deployment was superseded', result.stderr) - self.assertNotIn('get-pipeline-execution ', calls) - - def test_rechecks_latest_digest_after_cutover(self): - result, calls = self.poll({'list-pipeline-executions': [ - {'json': [execution()]}, - {'json': [execution(), execution(1001, digest=OTHER_DIGEST, identifier='execution-newer')]}, - ]}) - self.assertNotEqual(result.returncode, 0) - self.assertIn('deployment was superseded', result.stderr) - self.assertIn('get-deployment-target ', calls) - self.assertNotIn('Traffic cutover complete', result.stdout) - - def test_rechecks_execution_identity_for_same_digest_after_cutover(self): - result, _ = self.poll({'list-pipeline-executions': [ - {'json': [execution()]}, - {'json': [execution(), execution(1001, identifier='execution-newer')]}, - ]}) - self.assertNotEqual(result.returncode, 0) - self.assertIn('newer pipeline execution appeared', result.stdout) - self.assertNotIn('Traffic cutover complete', result.stdout) - - def test_iso_timestamps(self): - result, _ = self.poll({'list-pipeline-executions': {'json': [ - execution('1970-01-01T00:16:40+00:00')]}}) - self.assertEqual(result.returncode, 0, result.stderr) - - def test_scripted_responses_reject_unexpected_extra_calls(self): - result, _ = self.poll({'list-pipeline-executions': [{'json': [execution()]}]}) - self.assertNotEqual(result.returncode, 0) - self.assertIn('Unexpected extra AWS call: list-pipeline-executions', result.stderr) - self.assertNotIn('Traffic cutover complete', result.stdout) - - def test_access_denial_fails_immediately(self): - result, calls = self.poll({'list-pipeline-executions': {'error': 'AccessDeniedException'}}) - self.assertNotEqual(result.returncode, 0) - self.assertIn('AccessDeniedException', result.stderr) - self.assertEqual(len(calls.splitlines()), 1) - - def test_credentials_expiring_during_target_poll_fail(self): - result, _ = self.poll({'get-deployment-target:target-two': {'error': 'ExpiredToken'}}) - self.assertNotEqual(result.returncode, 0) - self.assertIn('ExpiredToken', result.stderr) - - def test_failed_and_superseded_pipeline_never_reach_deployment(self): - for status in ('Failed', 'Stopped', 'Superseded'): - with self.subTest(status=status): - result, calls = self.poll({'get-pipeline-execution': {'text': status}}) - self.assertNotEqual(result.returncode, 0) - self.assertNotIn('get-deployment ', calls) - - def test_waits_for_queued_deploy_action(self): - result, calls = self.poll({'list-action-executions': [ - {'json': []}, {'json': [deploy_action()]}]}) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(calls.count('list-action-executions '), 2) - - def test_finds_live_deployment_before_action_history_has_output(self): - result, calls = self.poll() - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn('get-pipeline-state --name app-pipeline', calls) - self.assertIn('get-deployment --deployment-id d-current', calls) - self.assertIn('Traffic cutover complete', result.stdout) - - def test_waits_for_state_from_the_exact_pipeline_and_action(self): - for stale in (None, deploy_state(execution_id='execution-old'), - deploy_state(action_id='action-old'), deploy_state(deployment_id='')): - with self.subTest(stale=stale): - result, calls = self.poll({'get-pipeline-state': [ - {'json': stale}, {'json': deploy_state()}]}) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(calls.count('get-pipeline-state '), 2) - self.assertEqual(calls.count('get-deployment '), 1) - - def test_old_live_action_cannot_satisfy_a_retry(self): - result, calls = self.poll({ - 'list-action-executions': {'json': [deploy_action(), deploy_action('action-old', start=999)]}, - 'get-pipeline-state': [ - {'json': deploy_state(action_id='action-old', deployment_id='d-old')}, - {'json': deploy_state()}], - }) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertNotIn('--deployment-id d-old', calls) - - def test_live_retry_waits_for_history_to_include_the_same_attempt(self): - for previous_status in ('Failed', 'Abandoned'): - with self.subTest(previous_status=previous_status): - previous = deploy_action('action-old', start=999, status=previous_status) - result, calls = self.poll({ - 'list-action-executions': [ - {'json': [previous]}, - {'json': [previous, deploy_action()]}, - ], - }) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(calls.count('get-pipeline-state '), 2) - self.assertEqual(calls.count('get-deployment '), 1) - self.assertIn('get-deployment --deployment-id d-current', calls) - - def test_bad_live_state_and_failed_actions_fail_closed(self): - for updates in ( - {'get-pipeline-state': {'error': 'AccessDeniedException'}}, - {'get-pipeline-state': {'json': deploy_state(deployment_id='wrong-provider-id')}}, - {'list-action-executions': {'json': [deploy_action(status='Failed')]}}, - ): - with self.subTest(updates=updates): - result, calls = self.poll(updates) - self.assertNotEqual(result.returncode, 0) - self.assertNotIn('get-deployment ', calls) - - def test_failed_deployment_never_accepts_old_cutover(self): - result, calls = self.poll({'get-deployment': {'text': 'Failed'}}) - self.assertNotEqual(result.returncode, 0) - self.assertNotIn('get-deployment-target ', calls) - - def test_empty_targets_cannot_satisfy_gate(self): - result, _ = self.poll({'list-deployment-targets': {'text': ''}}) - self.assertNotEqual(result.returncode, 0) - self.assertIn('timed out', result.stdout) - - def test_failed_target_fails_immediately(self): - result, _ = self.poll({'get-deployment-target:target-two': {'text': 'Failed'}}) - self.assertNotEqual(result.returncode, 0) - self.assertIn('cutover status Failed', result.stdout) - - def test_unchanged_image_verifies_existing_cutover(self): - result, calls = self.poll({'list-pipeline-executions': {'json': [execution(start=900)]}}, since='0') - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn('get-deployment-target ', calls) - - def test_unchanged_image_rejects_latest_different_deploy(self): - result, calls = self.poll({'list-pipeline-executions': {'json': [ - execution(start=900), execution(start=999, digest=OTHER_DIGEST)]}}, since='0') - self.assertNotEqual(result.returncode, 0) - self.assertIn('cutover is unverified', result.stderr) - self.assertNotIn('get-deployment ', calls) - - def test_unchanged_image_rejects_failed_previous_deploy(self): - result, _ = self.poll({'get-deployment': {'text': 'Failed'}}, since='0') - self.assertNotEqual(result.returncode, 0) - - def test_invalid_metadata_fails_before_aws(self): - result, calls = self.poll(since='corrupted') - self.assertNotEqual(result.returncode, 0) - self.assertEqual(calls, '') - - def test_ecr_digest_and_missing_tag(self): - result, _ = self.run_script('get-ecr-image-digest.sh', ['app', 'deploy'], { - 'batch-get-image': {'json': {'images': [{'imageId': {'imageDigest': DIGEST}}], 'failures': []}}}) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout.strip(), DIGEST) - missing = {'batch-get-image': {'json': {'images': [], 'failures': [{'failureCode': 'ImageNotFound'}]}}} - result, _ = self.run_script('get-ecr-image-digest.sh', ['app', 'deploy', '--allow-missing'], missing) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(result.stdout.strip(), '') - result, _ = self.run_script('get-ecr-image-digest.sh', ['app', 'deploy'], missing) - self.assertNotEqual(result.returncode, 0) - - def test_ecr_response_failures_are_not_missing_images(self): - for response in ({'error': 'AccessDeniedException'}, {'json': {'images': [], 'failures': [{'failureCode': 'KmsError'}]}}, {'json': {'images': [], 'failures': []}}): - with self.subTest(response=response): - result, _ = self.run_script('get-ecr-image-digest.sh', ['app', 'deploy', '--allow-missing'], {'batch-get-image': response}) - self.assertNotEqual(result.returncode, 0) - - def test_tag_move_uses_push_boundary_and_final_manifest_digest(self): - result, calls = self.run_script('promote-app-image.sh', ['registry', 'app', 'commit-dev', 'dev'], { - 'batch-get-image': [ - {'advance_clock': 30, 'json': {'images': [{'imageId': {'imageDigest': DIGEST}}], 'failures': []}}, - {'json': {'images': [{'imageId': {'imageDigest': OTHER_DIGEST}}], 'failures': []}}, - ]}) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn('retag_epoch=1030', result.github_output) - self.assertIn(f'app_image_digest={OTHER_DIGEST}', result.github_output) - self.assertIn('app_image_changed=true', result.github_output) - self.assertEqual([line.split()[0] for line in calls.splitlines()], ['ecr', 'docker', 'ecr']) - self.assertIn('registry/app:commit-dev', calls) - - def test_tag_move_aborts_before_docker_when_ecr_read_fails(self): - result, calls = self.run_script('promote-app-image.sh', ['registry', 'app', 'commit', 'deploy'], { - 'batch-get-image': {'error': 'AccessDeniedException'}}) - self.assertNotEqual(result.returncode, 0) - self.assertNotIn('docker', calls) - self.assertEqual(result.github_output, '') - - def test_same_digest_tag_move_reports_unchanged(self): - result, _ = self.run_script('promote-app-image.sh', ['registry', 'app', 'commit', 'deploy'], { - 'batch-get-image': {'json': {'images': [{'imageId': {'imageDigest': DIGEST}}], 'failures': []}}}) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn('app_image_changed=false', result.github_output) - - -class ReleaseOrderingTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - workflow = SCRIPTS.parent / 'workflows' / 'ci.yml' - parsed = subprocess.run([ - 'bun', '-e', 'console.log(JSON.stringify(Bun.YAML.parse(await Bun.file(process.argv[1]).text())))', - str(workflow)], check=True, capture_output=True, text=True) - cls.jobs = json.loads(parsed.stdout)['jobs'] - - def eligible(self, job, branch, results, event='push', cancelled=False, promoted='true'): - expression = self.jobs[job]['if'] - expression = re.sub(r'needs\.([\w-]+)\.result', lambda m: repr(results[m[1]]), expression) - expression = expression.replace('needs.promote-images.outputs.promoted', repr(promoted)) - expression = expression.replace('github.ref', repr('refs/heads/' + branch)) - expression = expression.replace('github.event_name', repr(event)) - expression = expression.replace('!cancelled()', repr(not cancelled)) - expression = expression.replace('&&', ' and ').replace('||', ' or ') - return eval(' '.join(expression.split()), {'__builtins__': {}}) - - def release_results(self, branch): - active = ('migrate-dev', 'build-dev', 'deploy-trigger-dev') if branch == 'dev' else ( - 'migrate', 'build-amd64', 'deploy-trigger') - results = {name: 'success' if name in active else 'skipped' - for name in self.jobs['promote-images']['needs']} - return active, results - - def test_uploads_and_image_builds_can_start_before_migration(self): - for job in ('deploy-trigger', 'deploy-trigger-dev', 'build-amd64', 'build-dev'): - self.assertFalse(self.jobs[job].get('needs'), job) - for job in ('deploy-trigger', 'deploy-trigger-dev'): - upload = next(step for step in self.jobs[job]['steps'] if step.get('id') == 'deploy') - self.assertIn('--skip-promotion', upload['run']) - - def test_each_release_waits_for_all_three_gates(self): - for branch in ('main', 'staging', 'dev'): - active, ready = self.release_results(branch) - self.assertTrue(self.eligible('promote-images', branch, ready)) - for gate in active: - self.assertIn(gate, self.jobs['promote-images']['needs']) - for failure in ('failure', 'cancelled', 'skipped'): - with self.subTest(branch=branch, gate=gate, result=failure): - self.assertFalse(self.eligible('promote-images', branch, {**ready, gate: failure})) - self.assertFalse(self.eligible('promote-images', branch, ready, cancelled=True)) - self.assertFalse(self.eligible('promote-images', branch, ready, event='pull_request')) - - def test_migrations_still_require_successful_tests(self): - self.assertIn('test-build', self.jobs['migrate']['needs']) - for branch in ('main', 'staging'): - self.assertTrue(self.eligible('migrate', branch, {'test-build': 'success'})) - for result in ('failure', 'cancelled', 'skipped'): - self.assertFalse(self.eligible('migrate', branch, {'test-build': result})) - - def test_dev_build_cannot_move_deploy_tags(self): - steps = self.jobs['build-dev']['steps'] - build = next(step for step in steps if step.get('uses') == './.github/actions/docker-build') - self.assertTrue(build['with']['tags'].endswith(':${{ github.sha }}-dev')) - self.assertNotIn('promote-app-image.sh', json.dumps(steps)) - self.assertNotIn('imagetools create', json.dumps(steps)) - - def test_task_promotion_requires_a_successful_fresh_app_release(self): - for branch, job, upload in (('main', 'promote-trigger', 'deploy-trigger'), - ('staging', 'promote-trigger', 'deploy-trigger'), - ('dev', 'promote-trigger-dev', 'deploy-trigger-dev')): - ready = {'promote-images': 'success', upload: 'success'} - self.assertIn('promote-images', self.jobs[job]['needs']) - self.assertTrue(self.eligible(job, branch, ready)) - self.assertFalse(self.eligible(job, branch, ready, promoted='false')) - self.assertFalse(self.eligible(job, branch, {**ready, 'promote-images': 'failure'})) - steps = self.jobs[job]['steps'] - wait = next(i for i, step in enumerate(steps) if 'wait-for-ecs-cutover.sh' in step.get('run', '')) - promote = next(i for i, step in enumerate(steps) if 'promote "$VERSION"' in step.get('run', '')) - self.assertLess(wait, promote) - self.assertEqual(steps[promote]['env']['VERSION'], '${{ needs.' + upload + '.outputs.version }}') - - def test_permission_check_and_other_images_precede_app_rollout(self): - steps = self.jobs['promote-images']['steps'] - preflight = next(i for i, step in enumerate(steps) if 'get-pipeline-state' in step.get('run', '')) - retag = next(i for i, step in enumerate(steps) if step.get('id') == 'promote') - self.assertLess(preflight, retag) - self.assertTrue(steps[retag]['env']['ECR_REPOS'].strip().endswith('${{ secrets.ECR_APP }}')) - - -if __name__ == '__main__': - unittest.main() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41aef55d921..9c110d101e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -223,154 +223,19 @@ jobs: tags: ${{ steps.login-ecr.outputs.registry }}/${{ steps.ecr-repo.outputs.name }}:${{ github.sha }}-dev max-cache-size-mb: ${{ matrix.cache_mb }} - # Upload without promotion in parallel; the release gate waits for the schema. - deploy-trigger-dev: - name: Deploy Trigger.dev (Dev) - if: github.event_name == 'push' && github.ref == 'refs/heads/dev' - runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} - timeout-minutes: 15 - outputs: - version: ${{ steps.deploy.outputs.deploymentVersion }} - steps: - - name: Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - - - name: Setup Bun - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - bun-version: 1.4.1 - - - name: Cache Bun dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 - with: - path: | - ~/.bun/install/cache - node_modules - **/node_modules - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install dependencies - run: bun install --frozen-lockfile --ignore-scripts - - - name: Deploy to Trigger.dev (skip promotion) - id: deploy - working-directory: ./apps/sim - env: - TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} - TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }} - run: | - set -eo pipefail - if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then - echo "ERROR: TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2 - exit 1 - fi - bunx trigger.dev@4.5.12 deploy --env preview --branch dev-sim --skip-promotion - - - name: Validate deployment version output - env: - VERSION: ${{ steps.deploy.outputs.deploymentVersion }} - run: | - if ! [[ "$VERSION" =~ ^[0-9]{8}\.[0-9]+$ ]]; then - echo "ERROR: Trigger.dev did not report a valid deploymentVersion output" >&2 - exit 1 - fi - - # Promote only after the gated app tag move, then observe live traffic cutover. - promote-trigger-dev: - name: Promote Trigger.dev (Dev) - needs: [promote-images, deploy-trigger-dev] - if: >- - !cancelled() && - github.event_name == 'push' && github.ref == 'refs/heads/dev' && - needs.promote-images.result == 'success' && - needs.promote-images.outputs.promoted == 'true' && - needs.deploy-trigger-dev.result == 'success' - runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} - # Dev bake is 5 min and dev deploys don't queue behind a bake (serialized by the - # ci- group), so a 20-min poll is ample; the 40-min job leaves ~20 min for - # setup + promote above it (mirrors the prod 90-vs-70 margin), and the 40-min - # session outlasts the poll. - timeout-minutes: 40 - permissions: - contents: read - id-token: write - steps: - - name: Checkout code - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - - - name: Setup Bun - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - bun-version: 1.4.1 - - - name: Cache Bun dependencies - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 - with: - path: | - ~/.bun/install/cache - node_modules - **/node_modules - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install dependencies - run: bun install --frozen-lockfile --ignore-scripts - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6 - with: - role-to-assume: ${{ secrets.DEV_AWS_ROLE_TO_ASSUME }} - aws-region: ${{ secrets.DEV_AWS_REGION }} - role-duration-seconds: 2400 - - - name: Wait for ECS traffic cutover - env: - OVERALL_TIMEOUT: "1200" - CHANGED: ${{ needs.promote-images.outputs.app_image_changed }} - DIGEST: ${{ needs.promote-images.outputs.app_image_digest }} - EPOCH: ${{ needs.promote-images.outputs.retag_epoch }} - run: | - set -eo pipefail - case "$CHANGED" in - true) ;; - false) EPOCH=0 ;; - *) echo "ERROR: invalid app image change metadata" >&2; exit 1 ;; - esac - bash .github/scripts/wait-for-ecs-cutover.sh sim-dev-us-east-1-app-deployment "$DIGEST" "$EPOCH" - - - name: Promote Trigger.dev version - working-directory: ./apps/sim - env: - TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} - TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }} - VERSION: ${{ needs.deploy-trigger-dev.outputs.version }} - run: | - set -eo pipefail - if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then - echo "ERROR: TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2 - exit 1 - fi - if [ -z "$VERSION" ]; then - echo "ERROR: no deployed version passed from deploy-trigger-dev" >&2 - exit 1 - fi - echo "Promoting Trigger.dev version $VERSION (preview / dev-sim)" - bunx trigger.dev@4.5.12 promote "$VERSION" --env preview --branch dev-sim - # Build and upload tasks alongside tests and images. The unpromoted version # cannot serve new runs; promote-images waits for it and successful migrations. - deploy-trigger: - name: Deploy Trigger.dev + prepare-trigger: + name: Prepare Trigger.dev if: >- github.event_name == 'push' && - (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') + (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/dev') runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 15 outputs: version: ${{ steps.deploy.outputs.deploymentVersion }} + environment: ${{ steps.target.outputs.environment }} + preview_branch: ${{ steps.target.outputs.preview_branch }} steps: - name: Checkout code uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 @@ -394,20 +259,37 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile --ignore-scripts - - name: Deploy to Trigger.dev (skip promotion) + - name: Select Trigger environment + id: target + run: | + case "$GITHUB_REF" in + refs/heads/main) TRIGGER_ENV=prod; TRIGGER_BRANCH='' ;; + refs/heads/staging) TRIGGER_ENV=staging; TRIGGER_BRANCH='' ;; + refs/heads/dev) TRIGGER_ENV=preview; TRIGGER_BRANCH=dev-sim ;; + *) echo "ERROR: unsupported Trigger release ref: $GITHUB_REF" >&2; exit 1 ;; + esac + echo "environment=$TRIGGER_ENV" >> "$GITHUB_OUTPUT" + echo "preview_branch=$TRIGGER_BRANCH" >> "$GITHUB_OUTPUT" + + - name: Upload Trigger.dev version without promotion id: deploy working-directory: ./apps/sim env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }} - TRIGGER_ENV: ${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }} + TRIGGER_ENV: ${{ steps.target.outputs.environment }} + TRIGGER_BRANCH: ${{ steps.target.outputs.preview_branch }} run: | set -eo pipefail if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then echo "ERROR: TRIGGER_ACCESS_TOKEN and TRIGGER_PROJECT_ID repo secrets must both be set" >&2 exit 1 fi - bunx trigger.dev@4.5.12 deploy --env "$TRIGGER_ENV" --skip-promotion + TARGET_ARGS=(--env "$TRIGGER_ENV") + if [ -n "$TRIGGER_BRANCH" ]; then + TARGET_ARGS+=(--branch "$TRIGGER_BRANCH") + fi + bunx trigger.dev@4.5.12 deploy "${TARGET_ARGS[@]}" --skip-promotion - name: Validate deployment version output env: @@ -552,19 +434,18 @@ jobs: # moves; a missing image can't produce a partial mixed-version deploy. promote-images: name: Promote Images - needs: [migrate, build-amd64, deploy-trigger, migrate-dev, build-dev, deploy-trigger-dev] + needs: [migrate, build-amd64, prepare-trigger, migrate-dev, build-dev] # Explicit results: see migrate's comment. if: >- !cancelled() && github.event_name == 'push' && + needs.prepare-trigger.result == 'success' && ( ((github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') && needs.migrate.result == 'success' && - needs.build-amd64.result == 'success' && - needs.deploy-trigger.result == 'success') || + needs.build-amd64.result == 'success') || (github.ref == 'refs/heads/dev' && needs.migrate-dev.result == 'success' && - needs.build-dev.result == 'success' && - needs.deploy-trigger-dev.result == 'success') + needs.build-dev.result == 'success') ) runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 10 @@ -665,7 +546,7 @@ jobs: fi done - # Main/staging: promote the parked Trigger.dev version after observing the ECS + # Promote the parked Trigger.dev version after observing the ECS # traffic cutover (CodeDeploy AllowTraffic on every target). The image retag # triggers the ECS pipeline; this job correlates it via the digest + retag epoch # (rejecting a stale execution reusing the digest) and promotes at cutover. @@ -674,20 +555,19 @@ jobs: # promote never fires and this job fails visibly. promote-trigger: name: Promote Trigger.dev - needs: [promote-images, deploy-trigger] + needs: [promote-images, prepare-trigger] # Explicit results also suppress skip propagation from optional ancestors. if: >- !cancelled() && github.event_name == 'push' && - (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') && + (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/dev') && needs.promote-images.result == 'success' && - needs.deploy-trigger.result == 'success' && + needs.prepare-trigger.result == 'success' && needs.promote-images.outputs.promoted == 'true' runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} - # Must exceed the poll script's OVERALL_TIMEOUT (70 min, covering a prod deploy - # queued behind a ~50-min bake) PLUS runner setup + the final promote step, so - # the Actions timeout never kills the job before the script's own deadline. - timeout-minutes: 90 + # Leave setup/promotion headroom above the cutover poll (dev: 20 min; + # staging/prod: 70 min, including a deploy queued behind a long bake). + timeout-minutes: ${{ github.ref == 'refs/heads/dev' && 40 || 90 }} permissions: contents: read id-token: write @@ -717,21 +597,19 @@ jobs: - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b # v6 with: - role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }} - aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }} - # The poll can run up to ~70 min (prod deploy queued behind a bake), which - # outlasts the default 1h session. Hold the session for the full job so AWS - # calls don't start failing mid-poll. Requires the deploy role's - # MaxSessionDuration to be >= this value (roles are managed outside the repo). - role-duration-seconds: 5400 + role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || github.ref == 'refs/heads/dev' && secrets.DEV_AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }} + aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || github.ref == 'refs/heads/dev' && secrets.DEV_AWS_REGION || secrets.STAGING_AWS_REGION }} + # Match each environment's session budget; both outlast their polls. + role-duration-seconds: ${{ github.ref == 'refs/heads/dev' && 2400 || 5400 }} # An unchanged tag may belong to a failed or still-running earlier deploy. # Verify its latest cutover rather than treating tag equality as success. - name: Wait for ECS traffic cutover env: + OVERALL_TIMEOUT: ${{ github.ref == 'refs/heads/dev' && 1200 || 4200 }} APP_IMAGE_CHANGED: ${{ needs.promote-images.outputs.app_image_changed }} DIGEST: ${{ needs.promote-images.outputs.app_image_digest }} - PIPELINE: sim-${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}-us-east-1-app-deployment + PIPELINE: sim-${{ github.ref == 'refs/heads/main' && 'production' || github.ref == 'refs/heads/dev' && 'dev' || 'staging' }}-us-east-1-app-deployment RETAG_EPOCH: ${{ needs.promote-images.outputs.retag_epoch }} run: | set -eo pipefail @@ -747,8 +625,9 @@ jobs: env: TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} TRIGGER_PROJECT_ID: ${{ secrets.TRIGGER_PROJECT_ID }} - TRIGGER_ENV: ${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }} - VERSION: ${{ needs.deploy-trigger.outputs.version }} + TRIGGER_ENV: ${{ needs.prepare-trigger.outputs.environment }} + TRIGGER_BRANCH: ${{ needs.prepare-trigger.outputs.preview_branch }} + VERSION: ${{ needs.prepare-trigger.outputs.version }} run: | set -eo pipefail if [ -z "$TRIGGER_ACCESS_TOKEN" ] || [ -z "$TRIGGER_PROJECT_ID" ]; then @@ -756,11 +635,15 @@ jobs: exit 1 fi if [ -z "$VERSION" ]; then - echo "ERROR: no deployed version passed from deploy-trigger" >&2 + echo "ERROR: no deployed version passed from prepare-trigger" >&2 exit 1 fi echo "Promoting Trigger.dev version $VERSION ($TRIGGER_ENV)" - bunx trigger.dev@4.5.12 promote "$VERSION" --env "$TRIGGER_ENV" + TARGET_ARGS=(--env "$TRIGGER_ENV") + if [ -n "$TRIGGER_BRANCH" ]; then + TARGET_ARGS+=(--branch "$TRIGGER_BRANCH") + fi + bunx trigger.dev@4.5.12 promote "$VERSION" "${TARGET_ARGS[@]}" # Build ARM64 images for GHCR (main branch only, runs in parallel with # tests). Pushes only the immutable sha tag — latest-arm64/version-arm64 diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index c2ac8153946..ee25a9bc578 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -370,9 +370,6 @@ jobs: - name: Lint code run: bun run lint:check - - name: Test Trigger deployment gates - run: python3 .github/scripts/test-trigger-deploy.py - # Every zero-argument `check:*` script, run concurrently. The list is derived in # scripts/run-audits.ts, which also writes the per-audit timing table to the job # summary and annotates failures. Audits needing a base ref stay separate below. From 25a2136910c14961a24f1b0e9c08c01ed1f6127e Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 16 Sep 2026 19:30:23 -0700 Subject: [PATCH 36/43] feat(coda): add Coda integration with API-token credential and resource pickers (#7907) * feat(coda): add Coda integration with API-token credential and resource pickers * chore(coda): regenerate artifacts and fix block registry audit false positive * fix(coda): retry field-setting PATCH updates on rate limits and server errors * fix(coda): declare nullable outputs and harden review edge cases --- apps/docs/components/icons.tsx | 11 + apps/docs/components/ui/icon-mapping.ts | 2 + apps/docs/content/docs/cli/reference.mdx | 4 +- apps/docs/content/docs/cli/selectors.mdx | 4 +- apps/docs/content/docs/integrations/coda.mdx | 1534 +++++++++++++ apps/docs/content/docs/integrations/meta.json | 1 + apps/docs/openapi-v2-workflows.json | 18 + apps/sim/blocks/blocks/coda.ts | 1995 +++++++++++++++++ apps/sim/blocks/registry-maps.ts | 3 + apps/sim/components/icons.tsx | 11 + .../sim/lib/block-metadata/names.generated.ts | 1 + .../lib/copilot/generated/docs-manifest.ts | 1 + .../token-service-accounts/descriptors.ts | 19 + .../token-service-accounts/server.ts | 3 + .../validators/coda.test.ts | 97 + .../token-service-accounts/validators/coda.ts | 64 + .../integrations/credential-display.test.ts | 1 + apps/sim/lib/integrations/icon-mapping.ts | 2 + apps/sim/lib/oauth/oauth.ts | 18 + apps/sim/lib/oauth/types.ts | 2 + apps/sim/lib/selectors/manifest.test.ts | 6 +- apps/sim/lib/selectors/manifest.ts | 43 + .../selectors/server/providers/coda.test.ts | 179 ++ .../lib/selectors/server/providers/coda.ts | 369 +++ apps/sim/lib/selectors/server/registry.ts | 2 + apps/sim/lib/selectors/types.ts | 1 + apps/sim/scripts/check-block-registry.ts | 48 +- apps/sim/tools/coda/add_custom_domain.ts | 47 + apps/sim/tools/coda/add_permission.ts | 107 + apps/sim/tools/coda/change_user_role.ts | 69 + apps/sim/tools/coda/coda.live.test.ts | 1135 ++++++++++ apps/sim/tools/coda/coda.test.ts | 424 ++++ apps/sim/tools/coda/create_doc.ts | 171 ++ apps/sim/tools/coda/create_folder.ts | 62 + apps/sim/tools/coda/create_page.ts | 144 ++ apps/sim/tools/coda/delete_custom_domain.ts | 50 + apps/sim/tools/coda/delete_doc.ts | 39 + apps/sim/tools/coda/delete_folder.ts | 39 + apps/sim/tools/coda/delete_page.ts | 42 + apps/sim/tools/coda/delete_page_content.ts | 73 + apps/sim/tools/coda/delete_permission.ts | 58 + apps/sim/tools/coda/delete_row.ts | 49 + apps/sim/tools/coda/delete_rows.ts | 64 + apps/sim/tools/coda/export_page.ts | 55 + apps/sim/tools/coda/get_acl_settings.ts | 45 + .../tools/coda/get_analytics_last_updated.ts | 53 + apps/sim/tools/coda/get_column.ts | 59 + apps/sim/tools/coda/get_control.ts | 72 + .../tools/coda/get_custom_domain_provider.ts | 54 + apps/sim/tools/coda/get_doc.ts | 43 + .../tools/coda/get_doc_analytics_summary.ts | 76 + apps/sim/tools/coda/get_folder.ts | 42 + apps/sim/tools/coda/get_formula.ts | 63 + apps/sim/tools/coda/get_mutation_status.ts | 55 + apps/sim/tools/coda/get_page.ts | 43 + apps/sim/tools/coda/get_page_content.ts | 110 + apps/sim/tools/coda/get_page_export_status.ts | 90 + apps/sim/tools/coda/get_row.ts | 68 + apps/sim/tools/coda/get_sharing_metadata.ts | 57 + apps/sim/tools/coda/get_table.ts | 58 + apps/sim/tools/coda/index.ts | 125 ++ apps/sim/tools/coda/list_categories.ts | 48 + apps/sim/tools/coda/list_columns.ts | 80 + apps/sim/tools/coda/list_controls.ts | 73 + apps/sim/tools/coda/list_custom_domains.ts | 86 + apps/sim/tools/coda/list_doc_analytics.ts | 275 +++ apps/sim/tools/coda/list_docs.ts | 120 + apps/sim/tools/coda/list_folder_children.ts | 86 + apps/sim/tools/coda/list_folders.ts | 77 + apps/sim/tools/coda/list_formulas.ts | 72 + apps/sim/tools/coda/list_page_analytics.ts | 179 ++ apps/sim/tools/coda/list_pages.ts | 66 + apps/sim/tools/coda/list_permissions.ts | 72 + apps/sim/tools/coda/list_rows.ts | 123 + apps/sim/tools/coda/list_tables.ts | 81 + apps/sim/tools/coda/list_workspace_members.ts | 150 ++ apps/sim/tools/coda/list_workspace_roles.ts | 76 + apps/sim/tools/coda/publish_doc.ts | 80 + apps/sim/tools/coda/push_button.ts | 70 + apps/sim/tools/coda/resolve_browser_link.ts | 95 + apps/sim/tools/coda/search_principals.ts | 96 + apps/sim/tools/coda/trigger_automation.ts | 69 + apps/sim/tools/coda/types.ts | 780 +++++++ apps/sim/tools/coda/unpublish_doc.ts | 39 + apps/sim/tools/coda/update_acl_settings.ts | 84 + apps/sim/tools/coda/update_doc.ts | 62 + apps/sim/tools/coda/update_folder.ts | 70 + apps/sim/tools/coda/update_page.ts | 128 ++ apps/sim/tools/coda/update_row.ts | 75 + apps/sim/tools/coda/upsert_rows.ts | 90 + apps/sim/tools/coda/utils.ts | 1106 +++++++++ apps/sim/tools/coda/whoami.ts | 73 + apps/sim/tools/error-extractors.ts | 53 + apps/sim/tools/generated/tool-ids.ts | 2 +- apps/sim/tools/generated/tool-metadata.ts | 2 +- apps/sim/tools/generated/tool-outputs.ts | 2 +- apps/sim/tools/registry.ts | 122 + .../src/integration-metadata.ts | 2 +- .../deployment-config/src/integrations.json | 261 ++- packages/sim-cli/src/generated/v2-api.ts | 36 + 100 files changed, 13216 insertions(+), 25 deletions(-) create mode 100644 apps/docs/content/docs/integrations/coda.mdx create mode 100644 apps/sim/blocks/blocks/coda.ts create mode 100644 apps/sim/lib/credentials/token-service-accounts/validators/coda.test.ts create mode 100644 apps/sim/lib/credentials/token-service-accounts/validators/coda.ts create mode 100644 apps/sim/lib/selectors/server/providers/coda.test.ts create mode 100644 apps/sim/lib/selectors/server/providers/coda.ts create mode 100644 apps/sim/tools/coda/add_custom_domain.ts create mode 100644 apps/sim/tools/coda/add_permission.ts create mode 100644 apps/sim/tools/coda/change_user_role.ts create mode 100644 apps/sim/tools/coda/coda.live.test.ts create mode 100644 apps/sim/tools/coda/coda.test.ts create mode 100644 apps/sim/tools/coda/create_doc.ts create mode 100644 apps/sim/tools/coda/create_folder.ts create mode 100644 apps/sim/tools/coda/create_page.ts create mode 100644 apps/sim/tools/coda/delete_custom_domain.ts create mode 100644 apps/sim/tools/coda/delete_doc.ts create mode 100644 apps/sim/tools/coda/delete_folder.ts create mode 100644 apps/sim/tools/coda/delete_page.ts create mode 100644 apps/sim/tools/coda/delete_page_content.ts create mode 100644 apps/sim/tools/coda/delete_permission.ts create mode 100644 apps/sim/tools/coda/delete_row.ts create mode 100644 apps/sim/tools/coda/delete_rows.ts create mode 100644 apps/sim/tools/coda/export_page.ts create mode 100644 apps/sim/tools/coda/get_acl_settings.ts create mode 100644 apps/sim/tools/coda/get_analytics_last_updated.ts create mode 100644 apps/sim/tools/coda/get_column.ts create mode 100644 apps/sim/tools/coda/get_control.ts create mode 100644 apps/sim/tools/coda/get_custom_domain_provider.ts create mode 100644 apps/sim/tools/coda/get_doc.ts create mode 100644 apps/sim/tools/coda/get_doc_analytics_summary.ts create mode 100644 apps/sim/tools/coda/get_folder.ts create mode 100644 apps/sim/tools/coda/get_formula.ts create mode 100644 apps/sim/tools/coda/get_mutation_status.ts create mode 100644 apps/sim/tools/coda/get_page.ts create mode 100644 apps/sim/tools/coda/get_page_content.ts create mode 100644 apps/sim/tools/coda/get_page_export_status.ts create mode 100644 apps/sim/tools/coda/get_row.ts create mode 100644 apps/sim/tools/coda/get_sharing_metadata.ts create mode 100644 apps/sim/tools/coda/get_table.ts create mode 100644 apps/sim/tools/coda/index.ts create mode 100644 apps/sim/tools/coda/list_categories.ts create mode 100644 apps/sim/tools/coda/list_columns.ts create mode 100644 apps/sim/tools/coda/list_controls.ts create mode 100644 apps/sim/tools/coda/list_custom_domains.ts create mode 100644 apps/sim/tools/coda/list_doc_analytics.ts create mode 100644 apps/sim/tools/coda/list_docs.ts create mode 100644 apps/sim/tools/coda/list_folder_children.ts create mode 100644 apps/sim/tools/coda/list_folders.ts create mode 100644 apps/sim/tools/coda/list_formulas.ts create mode 100644 apps/sim/tools/coda/list_page_analytics.ts create mode 100644 apps/sim/tools/coda/list_pages.ts create mode 100644 apps/sim/tools/coda/list_permissions.ts create mode 100644 apps/sim/tools/coda/list_rows.ts create mode 100644 apps/sim/tools/coda/list_tables.ts create mode 100644 apps/sim/tools/coda/list_workspace_members.ts create mode 100644 apps/sim/tools/coda/list_workspace_roles.ts create mode 100644 apps/sim/tools/coda/publish_doc.ts create mode 100644 apps/sim/tools/coda/push_button.ts create mode 100644 apps/sim/tools/coda/resolve_browser_link.ts create mode 100644 apps/sim/tools/coda/search_principals.ts create mode 100644 apps/sim/tools/coda/trigger_automation.ts create mode 100644 apps/sim/tools/coda/types.ts create mode 100644 apps/sim/tools/coda/unpublish_doc.ts create mode 100644 apps/sim/tools/coda/update_acl_settings.ts create mode 100644 apps/sim/tools/coda/update_doc.ts create mode 100644 apps/sim/tools/coda/update_folder.ts create mode 100644 apps/sim/tools/coda/update_page.ts create mode 100644 apps/sim/tools/coda/update_row.ts create mode 100644 apps/sim/tools/coda/upsert_rows.ts create mode 100644 apps/sim/tools/coda/utils.ts create mode 100644 apps/sim/tools/coda/whoami.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 867a81af5c2..165cd584113 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -2326,6 +2326,17 @@ export function AtlassianIcon(props: SVGProps) { ) } +export function CodaIcon(props: SVGProps) { + return ( + + + + ) +} + export function ConfluenceIcon(props: SVGProps) { const id = useId() const topGradientId = `confluence_top_${id}` diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 6178ca24e12..b01ec77b994 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -61,6 +61,7 @@ import { CloudflareIcon, CloudTrailIcon, CloudWatchIcon, + CodaIcon, CodeIcon, CodePipelineIcon, ConditionalIcon, @@ -358,6 +359,7 @@ export const blockTypeToIconMap: Record = { cloudformation: CloudFormationIcon, cloudtrail: CloudTrailIcon, cloudwatch: CloudWatchIcon, + coda: CodaIcon, codepipeline: CodePipelineIcon, condition: ConditionalIcon, confluence: ConfluenceIcon, diff --git a/apps/docs/content/docs/cli/reference.mdx b/apps/docs/content/docs/cli/reference.mdx index d793c3cae88..0a92390a38c 100644 --- a/apps/docs/content/docs/cli/reference.mdx +++ b/apps/docs/content/docs/cli/reference.mdx @@ -3052,7 +3052,7 @@ sim selectors get [options] | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. (JSON, or @path / @- to read a file or stdin). | | `--id ` | Yes | Resource identifier. | @@ -3072,7 +3072,7 @@ sim selectors list [options] | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. (JSON, or @path / @- to read a file or stdin). | | `--search ` | No | Provider option search text. | | `--cursor ` | No | Continue from nextCursor returned by a previous result. | diff --git a/apps/docs/content/docs/cli/selectors.mdx b/apps/docs/content/docs/cli/selectors.mdx index e728cb93a0e..4ff6c71849b 100644 --- a/apps/docs/content/docs/cli/selectors.mdx +++ b/apps/docs/content/docs/cli/selectors.mdx @@ -21,7 +21,7 @@ Get Selector Option (OAuth login or personal API key required) | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. (JSON, or @path / @- to read a file or stdin). | | `--id ` | Yes | Resource identifier. | @@ -41,7 +41,7 @@ List Selector Options (OAuth login or personal API key required) | Option | Required | Description | | --- | --- | --- | -| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | +| `--selector-key ` | Yes | Registered selector key for discovering this field’s destination options. Accepted values: `airtable.bases`, `airtable.tables`, `asana.workspaces`, `attio.lists`, `attio.objects`, `bigquery.datasets`, `bigquery.tables`, `bitbucket.workspaces`, `bitbucket.repositories`, `calcom.eventTypes`, `calcom.schedules`, `clickup.workspaces`, `clickup.spaces`, `clickup.folders`, `clickup.lists`, `coda.docs`, `coda.pages`, `coda.tables`, `coda.columns`, `coda.rows`, `coda.formulas`, `coda.controls`, `coda.folders`, `coda.permissions`, `confluence.spaces`, `confluence.spacesById`, `confluence.pages`, `google.tasks.lists`, `gmail.labels`, `google.calendar`, `google.drive`, `google.sheets`, `harmonic.savedSearches`, `hubspot.lists`, `hubspot.owners`, `hubspot.pipelines`, `hubspot.pipelineStages`, `hubspot.properties`, `jsm.requestTypes`, `jsm.serviceDesks`, `microsoft.planner.plans`, `notion.databases`, `notion.pages`, `netsuite.recordTypes`, `netsuite.asyncTasks`, `pipedrive.pipelines`, `sharepoint.lists`, `trello.boards`, `zoho_desk.organizations`, `zoho_desk.departments`, `zoho_desk.agents`, `zoom.meetings`, `slack.channels`, `snowflake.databases`, `snowflake.schemas`, `snowflake.tables`, `snowflake.warehouses`, `snowflake.roles`, `snowflake.fileFormats`, `snowflake.procedures`, `slack.users`, `outlook.folders`, `outlook.calendars`, `microsoft.teams`, `microsoft.chats`, `microsoft.channels`, `microsoft.planner`, `onedrive.files`, `onedrive.folders`, `sharepoint.sites`, `microsoft.excel`, `microsoft.excel.drives`, `microsoft.excel.sheets`, `microsoft.word`, `wealthbox.contacts`, `jira.issues`, `jira.projects`, `jira.projectKeys`, `linear.projects`, `linear.teams`, `monday.boards`, `monday.groups`, `webflow.sites`, `webflow.collections`, `webflow.items`, `cloudwatch.logGroups`, `cloudwatch.logStreams`, `imap.mailboxes`, `mcp.tools`, `managedAgent.agents`, `managedAgent.environments`, `managedAgent.vaults`, `managedAgent.memoryStores`, `knowledge.documents`, `sim.workflows`, `table.columns`, `table.outputColumns`, `workspace.secretNames`, `workspace.sandboxes`, `providers.ollamaEmbeddingModels`, `providers.openrouterEmbeddingModels`. | | `--context ` | No | Only the dependencies declared by the selector, such as oauthCredential and channelId. Missing OAuth connections require human authorization. (JSON, or @path / @- to read a file or stdin). | | `--search ` | No | Provider option search text. | | `--cursor ` | No | Continue from nextCursor returned by a previous result. | diff --git a/apps/docs/content/docs/integrations/coda.mdx b/apps/docs/content/docs/integrations/coda.mdx new file mode 100644 index 00000000000..74ca3e30686 --- /dev/null +++ b/apps/docs/content/docs/integrations/coda.mdx @@ -0,0 +1,1534 @@ +--- +title: Coda +description: Read and write Coda docs, pages, tables, and rows +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Coda](https://coda.io/) (now Superhuman Docs) combines documents, tables, and automations in one doc. The Coda block lets your agents read and write that doc: create and publish docs, write pages in Markdown or HTML, read and update table rows, push row buttons, read formulas and controls, trigger automations, and manage sharing, folders, and analytics. + +## Authentication + +This integration uses a reusable Coda **API token** connection, not OAuth. In Coda, open **Account settings → API settings** and generate a token. A token can be unrestricted, or limited to specific docs or tables with read or read-and-write access; a restricted token can only reach what it was granted. Create a Coda connection from the block's **Coda Account** field. Sim checks the token once against Coda, stores it encrypted, and reuses the same connection across Coda blocks. You can replace or revoke it from credential settings. + +## Working with Coda data + +- **Pickers:** After you pick an account, the **Doc**, **Page**, **Table**, **Row**, **Column**, **Formula**, **Control**, **Folder**, and **Permission** fields list what that token can reach. Switch a field to advanced mode to enter an ID, or pass one from an earlier block. IDs are safer than names, because users can rename things in the doc. +- **Links to IDs:** **Resolve Browser Link** turns a Coda URL someone pasted into the resource type and ID the other operations need. +- **Changes apply in the background:** Row, page, publishing, and automation writes return a `requestId` right away and are applied a few seconds later. Check that a change finished with **Get Mutation Status**. Data you read can also lag a few seconds behind edits made in the browser. +- **Rows:** Pass rows as objects that map column IDs or names to values, for example `[{"Name": "Apple", "Price": 1.25}]`. Set **Upsert Key Columns** to update matching rows instead of adding duplicates. Inserts only work on base tables, not views. Turn on **Use Column Names** when reading rows to get values keyed by column name. +- **Incremental reads:** **List Rows** returns a `nextSyncToken`. Pass it back later as **Sync Token** to get only rows that changed since then. +- **Pagination:** List operations return a `nextPageToken`. Pass it as **Page Token** to get the next page. When a Page Token is set, the original filters and limit are reused automatically. Coda can change page sizes at any time, so keep paging until no token comes back. +- **Page content:** **Get Page Content** reads a page as plain-text lines, with element IDs you can target in **Update Page** or **Delete Page Content**. To delete content, enter element IDs, or turn on **Delete All Page Content** to clear the whole page. For the full page as Markdown or HTML, run **Export Page**, then call **Get Page Export Status** until `downloadLink` is present. The link expires shortly after it is issued. +- **Permissions and plans:** Creating docs and pages or renaming a doc requires Doc Maker access in the workspace. Publishing needs a Coda maker profile. Hiding pages and custom domains need a paid Coda plan. Workspace members, role activity, and role changes need a workspace that belongs to an organization, and role changes need Admin access. Page analytics are only available for docs in Enterprise workspaces. +- **Rate limits:** Coda limits requests per user, with tighter limits on writes and on listing docs. Sim automatically retries reads, updates, and deletes when Coda returns HTTP 429 or a transient server error. Inserts and creates are not retried, so space out bulk writes. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Coda (Superhuman Docs) into your workflow with a reusable API-token credential. Create, copy, publish, and share docs; create, update, read, export, and clear pages; read table schemas; list, insert, upsert, update, and delete rows and push row buttons; read formulas and controls; trigger webhook automations; manage folders, custom domains, and workspace roles; and read doc and page analytics. Pick docs, pages, tables, rows, and more from dropdowns populated by your account. + + + +## Actions + +### Coda Add Custom Domain + +Connect a custom domain to a published Coda doc. Requires a Coda plan with custom domains. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `customDocDomain` | string | Yes | The custom domain \(e.g., "docs.example.com"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docId` | string | ID of the doc | +| `customDocDomain` | string | The custom domain that was added | + +### Coda Share Doc + +Share a Coda doc with a user, group, domain, workspace, or anyone with the link. Sharing with an email sends a notification unless suppressed. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `access` | string | Yes | Access level to grant: "readonly", "comment", or "write" | +| `principalType` | string | Yes | Who to share with: "email", "group", "domain", "workspace", or "anyone" | +| `principal` | string | No | Email address, group ID, domain, or workspace ID matching principalType. Not used for "anyone". | +| `suppressEmail` | boolean | No | Do not send a sharing notification email | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docId` | string | ID of the shared doc | +| `access` | string | Access level granted | +| `principalType` | string | Type of principal the doc was shared with | + +### Coda Change User Role + +Change the workspace role of a Coda user. Requires Admin access in a workspace that belongs to an organization. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workspaceId` | string | Yes | ID of the workspace \(e.g., "ws-1Ab234"\) | +| `email` | string | Yes | Email address of the workspace member | +| `newRole` | string | Yes | New role: "Admin", "DocMaker", or "Editor" | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `email` | string | Email address of the member | +| `newRole` | string | Role assigned | +| `roleChangedAt` | string | When the role change took effect | + +### Coda Create Doc + +Create a Coda doc, optionally copying an existing doc and setting up its first page with Markdown, HTML, an embed, or a sync page. Requires Doc Maker access in the workspace. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `title` | string | No | Title of the new doc \(defaults to "Untitled"\) | +| `sourceDoc` | string | No | ID of an existing doc to copy | +| `timezone` | string | No | Timezone for the new doc \(e.g., "America/Los_Angeles"\) | +| `folderId` | string | No | ID of the folder to create the doc in \(defaults to "My docs"\) | +| `pageName` | string | No | Name of the initial page | +| `pageSubtitle` | string | No | Subtitle of the initial page | +| `iconName` | string | No | Icon name for the initial page \(e.g., "rocket"\) | +| `imageUrl` | string | No | Cover image URL for the initial page | +| `pageType` | string | No | Initial page content type: "canvas" \(default\), "embed", or "syncPage" | +| `contentFormat` | string | No | Canvas content format: "markdown" \(default\) or "html" | +| `content` | string | No | Canvas content for the initial page in the chosen format | +| `embedUrl` | string | No | URL to embed as a full page \(pageType "embed"\) | +| `renderMethod` | string | No | Embed render method: "standard" or "compatibility" | +| `sourceDocId` | string | No | Doc to sync from \(pageType "syncPage"\) | +| `sourcePageId` | string | No | Page to sync \(pageType "syncPage" with syncMode "page"\) | +| `syncMode` | string | No | Sync page mode: "page" \(default\) or "document" | +| `includeSubpages` | boolean | No | Include subpages in a single-page sync page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `doc` | object | The created doc | +| `requestId` | string | Coda request ID for the doc creation | + +### Coda Create Folder + +Create a folder in a Coda workspace + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `name` | string | Yes | Name of the folder | +| `workspaceId` | string | Yes | ID of the workspace \(e.g., "ws-1Ab234"\) | +| `description` | string | No | Description of the folder | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `folder` | object | The created folder | + +### Coda Create Page + +Create a page in a Coda doc, optionally as a subpage, with Markdown or HTML content, a full-page embed, or a sync page from another doc. The page is created asynchronously. Requires Doc Maker access. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `name` | string | No | Name of the page | +| `subtitle` | string | No | Subtitle of the page | +| `iconName` | string | No | Name of the page icon \(e.g., "rocket"\) | +| `imageUrl` | string | No | URL of a cover image for the page | +| `parentPageId` | string | No | ID of the parent page, to create this page as a subpage | +| `pageType` | string | No | Page content type: "canvas" \(default\), "embed", or "syncPage" | +| `contentFormat` | string | No | Canvas content format: "markdown" \(default\) or "html" | +| `content` | string | No | Canvas page content in the chosen format | +| `embedUrl` | string | No | URL to embed as a full page \(pageType "embed"\) | +| `renderMethod` | string | No | Embed render method: "standard" or "compatibility" | +| `sourceDocId` | string | No | Doc to sync from \(pageType "syncPage"\) | +| `sourcePageId` | string | No | Page to sync \(pageType "syncPage" with syncMode "page"\) | +| `syncMode` | string | No | Sync page mode: "page" \(default\) or "document" | +| `includeSubpages` | boolean | No | Include subpages in a single-page sync page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pageId` | string | ID of the created page | + +### Coda Delete Custom Domain + +Remove a custom domain from a published Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `customDocDomain` | string | Yes | The custom domain \(e.g., "docs.example.com"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docId` | string | ID of the doc | +| `customDocDomain` | string | The custom domain that was removed | + +### Coda Delete Doc + +Delete a Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docId` | string | ID of the deleted doc | + +### Coda Delete Folder + +Delete an empty Coda folder (it must contain no docs) + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `folderId` | string | Yes | ID of the folder \(e.g., "fl-1Ab234"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `folderId` | string | ID of the deleted folder | + +### Coda Delete Page + +Delete a page from a Coda doc. Applied asynchronously. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `pageId` | string | Yes | ID or name of the page \(IDs are recommended, e.g., "canvas-IjkLmnO"; names containing "/" are not supported\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pageId` | string | ID of the deleted page | + +### Coda Delete Page Content + +Delete specific content elements from a Coda page, or all of its content when no element IDs are given. Applied asynchronously. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `pageId` | string | Yes | ID or name of the page \(IDs are recommended, e.g., "canvas-IjkLmnO"; names containing "/" are not supported\) | +| `elementIds` | json | No | Element IDs to delete \(from Get Page Content\), as an array or comma-separated list | +| `deleteAll` | boolean | No | Set to true, with no element IDs, to delete all content from the page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pageId` | string | ID of the page whose content was deleted | + +### Coda Remove Permission + +Revoke a sharing permission on a Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `permissionId` | string | Yes | ID of the permission to remove \(from List Permissions\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docId` | string | ID of the doc | +| `permissionId` | string | ID of the removed permission | + +### Coda Delete Row + +Delete a row from a Coda table or view. Applied asynchronously. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableId` | string | Yes | ID or name of the table or view \(IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported\) | +| `rowId` | string | Yes | ID or name of the row \(IDs are recommended, e.g., "i-tuVwxYz"; names containing "/" are not supported\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `rowId` | string | ID of the deleted row | + +### Coda Delete Rows + +Delete multiple rows from a Coda table or view by ID. Applied asynchronously. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableId` | string | Yes | ID or name of the table or view \(IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported\) | +| `rowIds` | json | Yes | Row IDs to delete, as an array or comma-separated list \(e.g., \["i-bCdeFgh", "i-CdEfgHi"\]\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `rowIds` | array | IDs of the rows queued for deletion | + +### Coda Export Page + +Start exporting a Coda page as HTML or Markdown. Poll Get Page Export Status with the returned export ID for the download link. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `pageId` | string | Yes | ID or name of the page \(IDs are recommended, e.g., "canvas-IjkLmnO"; names containing "/" are not supported\) | +| `outputFormat` | string | Yes | Export format: "markdown" or "html" | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `exportId` | string | ID of the export request | +| `status` | string | Export status \(inProgress, failed, complete\) | +| `href` | string | API link that reports the export status | + +### Coda Get Sharing Settings + +Get the sharing settings of a Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docs` | json | Docs \(id, name, href, browserLink, icon, owner, ownerName, createdAt, updatedAt, workspace, folder, sourceDoc, docSize, published\) | +| `doc` | json | Doc \(id, name, href, browserLink, icon, owner, ownerName, createdAt, updatedAt, workspace \{id, name, organizationId\}, folder \{id, name\}, sourceDoc, docSize \{totalRowCount, tableAndViewCount, baseTableCount, pageCount, overApiSizeLimit\}, published \{description, browserLink, discoverable, mode, categories\}\) | +| `docId` | string | ID of the affected doc | +| `categories` | json | Doc category names | +| `requestId` | string | Request ID of a queued change, for Get Mutation Status | +| `customDomains` | json | Custom domains \(customDocDomain, hasCertificate, hasDnsDocId, setupStatus, domainStatus, lastVerifiedTimestamp\) | +| `customDocDomain` | string | Custom domain | +| `provider` | string | DNS provider of a custom domain | +| `permissions` | json | Permissions \(id, access, principal \{type, email, groupId, groupName, domain, workspaceId\}\) | +| `access` | string | Access level granted | +| `principalType` | string | Type of principal the doc was shared with | +| `permissionId` | string | ID of the removed permission | +| `users` | json | Matching users \(name, loginId, pictureLink\) | +| `groups` | json | Matching groups \(groupId, groupName\) | +| `canShare` | boolean | Whether the user can share the doc | +| `canShareWithWorkspace` | boolean | Whether the user can share with the workspace | +| `canShareWithOrg` | boolean | Whether the user can share with the org | +| `canCopy` | boolean | Whether the user can copy the doc | +| `allowEditorsToChangePermissions` | boolean | Whether editors can change permissions | +| `allowCopying` | boolean | Whether viewers can copy the doc | +| `allowViewersToRequestEditing` | boolean | Whether viewers can request editing | +| `pages` | json | Pages \(id, name, subtitle, browserLink, contentType, isHidden, icon, image, parent, children, authors, createdAt, updatedAt\) | +| `page` | json | Page \(id, name, subtitle, href, browserLink, contentType, isHidden, isEffectivelyHidden, icon, image, parent, children, authors, createdAt, createdBy, updatedAt, updatedBy\) | +| `pageId` | string | ID of the created, updated, or deleted page | +| `items` | json | Page content lines \(id, type, style, format, content, lineLevel\), or analytics items \(doc or page plus daily metrics\) | +| `exportId` | string | Page export ID | +| `status` | string | Page export status \(inProgress, failed, complete\) | +| `href` | string | API link reporting the page export status | +| `downloadLink` | string | Download link of a completed page export | +| `exportError` | string | Error message of a failed page export | +| `tables` | json | Tables and views \(id, name, tableType, href, browserLink, parent\) | +| `table` | json | Table \(id, name, tableType, browserLink, parent, parentTable, displayColumnId, rowCount, sorts, layout, filter, createdAt, updatedAt\) | +| `columns` | json | Columns \(id, name, display, calculated, formula, defaultValue, format\) | +| `column` | json | Column \(id, name, display, calculated, formula, defaultValue, format, parentTable\) | +| `rows` | json | Rows \(id, name, index, browserLink, createdAt, updatedAt, values\) | +| `row` | json | Row \(id, name, index, browserLink, createdAt, updatedAt, values, parentTable\) | +| `nextSyncToken` | string | Token for reading only rows changed later | +| `addedRowIds` | json | IDs of rows that will be added | +| `rowId` | string | ID of the affected row | +| `rowIds` | json | IDs of rows queued for deletion | +| `columnId` | string | ID of the pushed button column | +| `formulas` | json | Named formulas \(id, name, href, parent\) | +| `formula` | json | Formula \(id, name, href, parent, value\) | +| `controls` | json | Controls \(id, name, href, parent\) | +| `control` | json | Control \(id, name, href, parent, controlType, value\) | +| `folders` | json | Folders \(id, name, browserLink, description, icon, iconColor, createdAt, canEdit, workspace\) | +| `folder` | json | Folder \(id, name, browserLink, description, icon, iconColor, createdAt, canEdit, workspace\) | +| `folderId` | string | ID of the deleted folder | +| `children` | json | Subfolders \(id, name, browserLink, visibility, workspace, ...\) | +| `members` | json | Workspace members \(email, name, role, registeredAt, roleChangedAt, lastActiveAt, ownedDocs, totalDocs, ...\) | +| `email` | string | Email of the member whose role changed | +| `newRole` | string | Role assigned | +| `roleChangedAt` | string | When the role change took effect | +| `roleActivity` | json | Monthly role counts \(month, activeAdminCount, activeDocMakerCount, activeEditorCount, inactive counts\) | +| `totalSessions` | number | Total sessions across matching docs | +| `docAnalyticsLastUpdated` | string | Date doc analytics last updated | +| `packAnalyticsLastUpdated` | string | Date Pack analytics last updated | +| `packFormulaAnalyticsLastUpdated` | string | Date Pack formula analytics last updated | +| `browserLink` | string | Canonical browser link of a resolved resource | +| `resource` | json | Resolved resource \(type, id, name, href\) | +| `completed` | boolean | Whether a queued change was applied | +| `warning` | string | Warning for a change that completed with caveats | +| `name` | string | Name of the token owner | +| `loginId` | string | Email of the token owner | +| `pictureLink` | string | Avatar link of the token owner | +| `scoped` | boolean | Whether the token is restricted | +| `tokenName` | string | Name of the API token | +| `workspace` | json | Default workspace of the token owner \(id, name, organizationId, browserLink\) | +| `nextPageToken` | string | Token for fetching the next page of results | + +### Coda Get Analytics Last Updated + +Get the dates (Pacific time) Coda analytics were last refreshed, to know how current analytics data is + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docAnalyticsLastUpdated` | string | Date doc analytics last updated | +| `packAnalyticsLastUpdated` | string | Date Pack analytics last updated | +| `packFormulaAnalyticsLastUpdated` | string | Date Pack formula analytics last updated | + +### Coda Get Column + +Get details about a column in a Coda table, including its full format settings + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableId` | string | Yes | ID or name of the table or view \(IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported\) | +| `columnId` | string | Yes | ID or name of the column \(IDs are recommended, e.g., "c-tuVwxYz"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `column` | object | Column details | + +### Coda Get Control + +Get the type and current value of a control in a Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `controlId` | string | Yes | ID or name of the control \(IDs are recommended, e.g., "ctrl-cDefGhij"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `control` | object | Control details | +| ↳ `controlType` | string | Control type \(aiBlock, button, checkbox, datePicker, dateRangePicker, dateTimePicker, lookup, multiselect, select, scale, slider, reaction, textbox, timePicker\) | +| ↳ `value` | json | Current value \(string, number, boolean, or array of these\) | + +### Coda Get Custom Domain Provider + +Look up the DNS provider (GoDaddy, Namecheap, Hover, Network Solutions, Google Domains, or Other) of a custom domain + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `customDocDomain` | string | Yes | The custom domain \(e.g., "docs.example.com"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `customDocDomain` | string | The custom domain | +| `provider` | string | DNS provider of the domain | + +### Coda Get Doc + +Get metadata for a Coda doc, including its owner, workspace, folder, size, and publishing settings + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `doc` | object | Doc metadata | + +### Coda Get Doc Analytics Summary + +Get the total number of sessions across the Coda docs the user can access + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `isPublished` | boolean | No | Only include published docs | +| `sinceDate` | string | No | Only include activity on or after this date \(YYYY-MM-DD\) | +| `untilDate` | string | No | Only include activity on or before this date \(YYYY-MM-DD\) | +| `workspaceId` | string | No | Only include docs in this workspace | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `totalSessions` | number | Total sessions across all matching docs | + +### Coda Get Folder + +Get details about a Coda folder + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `folderId` | string | Yes | ID of the folder \(e.g., "fl-1Ab234"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `folder` | object | Folder details | + +### Coda Get Formula + +Get the current computed value of a named formula in a Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `formulaId` | string | Yes | ID or name of the formula \(IDs are recommended, e.g., "f-fgHijkLm"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `formula` | object | Formula details | +| ↳ `value` | json | Computed value \(string, number, boolean, or array of these\) | + +### Coda Get Mutation Status + +Check whether a queued Coda change (row, page, publish, or automation request) has been applied. Status is kept for about a day. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `requestId` | string | Yes | Request ID returned by a Coda write operation | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `completed` | boolean | Whether the change has been applied | +| `warning` | string | Warning if the change completed with caveats | + +### Coda Get Page + +Get metadata for a page in a Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `pageId` | string | Yes | ID or name of the page \(IDs are recommended, e.g., "canvas-IjkLmnO"; names containing "/" are not supported\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `page` | object | Page metadata | + +### Coda Get Page Content + +Read the content of a Coda canvas page as plain-text lines with their styles (headings, paragraphs, lists, quotes, code) and element IDs + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `pageId` | string | Yes | ID or name of the page \(IDs are recommended, e.g., "canvas-IjkLmnO"; names containing "/" are not supported\) | +| `limit` | number | No | Maximum number of content items to return \(1-500, default 50\) | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Content elements on the page, in order | +| ↳ `id` | string | Element ID, usable with Update Page and Delete Page Content | +| ↳ `type` | string | Element type \(line\) | +| ↳ `style` | string | Line style \(paragraph, h1, h2, h3, bulletedList, numberedList, checkboxList, collapsibleList, blockQuote, pullQuote, code\) | +| ↳ `format` | string | Content format \(plainText\) | +| ↳ `content` | string | Element text | +| ↳ `lineLevel` | number | Indentation level for paragraphs, quotes, and list items | + +### Coda Get Page Export Status + +Check a Coda page export and get its download link once complete. Download links expire shortly after they are issued. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `pageId` | string | Yes | ID or name of the page \(IDs are recommended, e.g., "canvas-IjkLmnO"; names containing "/" are not supported\) | +| `exportId` | string | Yes | Export ID returned by Export Page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `exportId` | string | ID of the export request | +| `status` | string | Export status \(inProgress, failed, complete\) | +| `href` | string | API link that reports the export status | +| `downloadLink` | string | Short-lived download link for the exported file, once complete | +| `exportError` | string | Error message if the export failed | + +### Coda Get Row + +Get a single row from a Coda table, including all of its cell values + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableId` | string | Yes | ID or name of the table or view \(IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported\) | +| `rowId` | string | Yes | ID or name of the row \(IDs are recommended, e.g., "i-tuVwxYz"; names containing "/" are not supported\) | +| `useColumnNames` | boolean | No | Key cell values by column name instead of column ID | +| `valueFormat` | string | No | Cell value format: "simple" \(default\), "simpleWithArrays", or "rich" | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `row` | object | Row details and values | + +### Coda Get Sharing Metadata + +Check whether the connected user can share or copy a Coda doc, and whether they can share it with the workspace or organization + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `canShare` | boolean | Whether the user can share the doc | +| `canShareWithWorkspace` | boolean | Whether the user can share the doc with the workspace | +| `canShareWithOrg` | boolean | Whether the user can share the doc with the organization | +| `canCopy` | boolean | Whether the user can copy the doc | + +### Coda Get Table + +Get details about a table or view in a Coda doc, including its row count, sorts, layout, and filter + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableId` | string | Yes | ID or name of the table or view \(IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported\) | +| `useUpdatedTableLayouts` | boolean | No | Report detail and form layouts as "detail" and "form" instead of "masterDetail" for both | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `table` | object | Table details | + +### Coda List Doc Categories + +List the categories that can be applied to a published Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `categories` | array | Category names usable when publishing a doc | + +### Coda List Columns + +List the columns of a Coda table with their IDs, formats, and formulas. Use column IDs when reading and writing rows. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableId` | string | Yes | ID or name of the table or view \(IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported\) | +| `visibleOnly` | boolean | No | Only return visible columns \(applies to base tables, not views\) | +| `limit` | number | No | Maximum number of columns to return \(1-100, default 25\) | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `columns` | array | Columns in the table | + +### Coda List Controls + +List the controls (sliders, selects, checkboxes, date pickers, buttons, etc.) in a Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `sortBy` | string | No | Sort order; "name" sorts alphabetically | +| `limit` | number | No | Maximum number of results to return per page | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `controls` | array | Controls in the doc | + +### Coda List Custom Domains + +List the custom domains connected to a published Coda doc and their setup status + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `customDomains` | array | Custom domains for the published doc | +| ↳ `customDocDomain` | string | The custom domain | +| ↳ `hasCertificate` | boolean | Whether the domain has a certificate | +| ↳ `hasDnsDocId` | boolean | Whether the domain DNS points back to this doc | +| ↳ `setupStatus` | string | Setup status \(pending, succeeded, failed\) | +| ↳ `domainStatus` | string | connected or notConnected | +| ↳ `lastVerifiedTimestamp` | string | When the DNS settings were last checked | + +### Coda List Doc Analytics + +Get per-day or cumulative analytics (views, copies, likes, sessions by device, AI credits) for Coda docs + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docIds` | json | No | Doc IDs to fetch analytics for, as an array or comma-separated list | +| `workspaceId` | string | No | Only include docs in this workspace | +| `query` | string | No | Search term used to filter docs | +| `isPublished` | boolean | No | Only include published docs | +| `sinceDate` | string | No | Only include activity on or after this date \(YYYY-MM-DD\) | +| `untilDate` | string | No | Only include activity on or before this date \(YYYY-MM-DD\) | +| `scale` | string | No | Aggregation: "daily" \(default\) or "cumulative" | +| `orderBy` | string | No | Sort field: date, docId, title, createdAt, publishedAt, likes, copies, views, sessionsDesktop, sessionsMobile, sessionsOther, totalSessions, or an aiCredits field | +| `direction` | string | No | Sort direction: "ascending" or "descending" | +| `limit` | number | No | Maximum number of results to return \(1-5000, default 1000\) | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Analytics per doc | +| ↳ `doc` | object | Doc the metrics belong to | +| ↳ `metrics` | array | Metrics per date | +| ↳ `date` | string | Date of the data \(YYYY-MM-DD\) | +| ↳ `views` | number | Doc views | +| ↳ `copies` | number | Doc copies | +| ↳ `likes` | number | Doc likes | +| ↳ `sessionsMobile` | number | Unique mobile visitors | +| ↳ `sessionsDesktop` | number | Unique desktop visitors | +| ↳ `sessionsOther` | number | Unique visitors on other devices | +| ↳ `totalSessions` | number | Sessions across all devices | +| ↳ `aiCreditsChat` | number | AI credits used by chat | +| ↳ `aiCreditsBlock` | number | AI credits used by AI blocks | +| ↳ `aiCreditsColumn` | number | AI credits used by AI columns | +| ↳ `aiCreditsAssistant` | number | AI credits used by the assistant | +| ↳ `aiCreditsReviewer` | number | AI credits used by the reviewer | +| ↳ `aiCredits` | number | Total AI credits used | + +### Coda List Docs + +List Coda docs the user has opened, most recently used first, filtered by search, owner, publishing, stars, workspace, folder, or source doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `query` | string | No | Search term used to filter docs | +| `isOwner` | boolean | No | Only return docs owned by the user | +| `isPublished` | boolean | No | Only return published docs | +| `isStarred` | boolean | No | true returns only starred docs; false returns only unstarred docs | +| `inGallery` | boolean | No | Only return docs visible in the gallery | +| `sourceDoc` | string | No | Only return docs copied from this doc ID | +| `workspaceId` | string | No | Only return docs in this workspace \(e.g., "ws-1Ab234"\) | +| `folderId` | string | No | Only return docs in this folder \(e.g., "fl-1Ab234"\) | +| `limit` | number | No | Maximum number of results to return per page | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docs` | array | Docs matching the filters | + +### Coda List Subfolders + +List the direct subfolders of a Coda folder. Subfolders you cannot access but manage the parent of are returned with only an ID and restricted visibility. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `folderId` | string | Yes | ID of the folder \(e.g., "fl-1Ab234"\) | +| `limit` | number | No | Maximum number of results to return per page | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `children` | array | Direct subfolders | +| ↳ `visibility` | string | visible, or restricted when only the ID is returned because you cannot access the subfolder | + +### Coda List Folders + +List the Coda folders the user can access, optionally within one workspace + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workspaceId` | string | No | Only return folders in this workspace \(e.g., "ws-1Ab234"\) | +| `isStarred` | boolean | No | true returns only starred folders; false returns only unstarred folders | +| `limit` | number | No | Maximum number of results to return per page | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `folders` | array | Folders the user can access | + +### Coda List Formulas + +List the named formulas in a Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `sortBy` | string | No | Sort order; "name" sorts alphabetically | +| `limit` | number | No | Maximum number of results to return per page | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `formulas` | array | Named formulas in the doc | + +### Coda List Page Analytics + +Get daily analytics (views, sessions, users, time viewed) for each page of a Coda doc. Only available for docs in Enterprise workspaces. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `sinceDate` | string | No | Only include activity on or after this date \(YYYY-MM-DD\) | +| `untilDate` | string | No | Only include activity on or before this date \(YYYY-MM-DD\) | +| `limit` | number | No | Maximum number of results to return \(1-5000, default 1000\) | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | Analytics per page | +| ↳ `page` | object | Page the metrics belong to | +| ↳ `metrics` | array | Metrics per date | +| ↳ `date` | string | Date of the data \(YYYY-MM-DD\) | +| ↳ `views` | number | Page views that day | +| ↳ `sessions` | number | Unique browsers that viewed the page | +| ↳ `users` | number | Unique Coda users that viewed the page | +| ↳ `averageSecondsViewed` | number | Average seconds the page was viewed | +| ↳ `medianSecondsViewed` | number | Median seconds the page was viewed | +| ↳ `tabs` | number | Unique tabs that opened the doc | + +### Coda List Pages + +List the pages in a Coda doc, including their hierarchy + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `limit` | number | No | Maximum number of results to return per page | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pages` | array | Pages in the doc | + +### Coda List Permissions + +List who a Coda doc is shared with and their access levels + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `limit` | number | No | Maximum number of results to return per page | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `permissions` | array | Permissions granted on the doc | + +### Coda List Rows + +List rows in a Coda table or view, optionally filtered by a column value, sorted, or limited to rows changed since a sync token + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableId` | string | Yes | ID or name of the table or view \(IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported\) | +| `query` | string | No | Filter as <column_id_or_name>:<JSON value>. Quote column names and string values, e.g., c-tuVwxYz:"Apple" or "Status":"Done" | +| `sortBy` | string | No | Sort order: "createdAt" \(default\), "updatedAt", or "natural" \(view order; implies visibleOnly\) | +| `useColumnNames` | boolean | No | Key cell values by column name instead of column ID | +| `valueFormat` | string | No | Cell value format: "simple" \(default\), "simpleWithArrays", or "rich" | +| `visibleOnly` | boolean | No | Only return visible rows and columns | +| `syncToken` | string | No | nextSyncToken from a previous call, to return only rows changed since then | +| `limit` | number | No | Maximum number of results to return per page | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `rows` | array | Rows in the table | +| `nextSyncToken` | string | Token to pass as syncToken later to fetch only rows changed after this call | + +### Coda List Tables + +List the tables and views in a Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableTypes` | json | No | Table types to include, as an array or comma-separated list of "table", "view", "database" \(defaults to all\) | +| `sortBy` | string | No | Sort order; "name" sorts alphabetically | +| `limit` | number | No | Maximum number of results to return per page | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `tables` | array | Tables and views in the doc | + +### Coda List Workspace Members + +List the members of a Coda workspace with their roles and doc activity, requesting user first. The workspace must belong to an organization. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workspaceId` | string | Yes | ID of the workspace \(e.g., "ws-1Ab234"\) | +| `includedRoles` | json | No | Only return members with these roles, as an array or comma-separated list of "Admin", "DocMaker", "Editor" | +| `pageToken` | string | No | Page token from a previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `members` | array | Workspace members | +| ↳ `email` | string | Email address | +| ↳ `name` | string | Name | +| ↳ `role` | string | Workspace role \(Admin, DocMaker, Editor\) | +| ↳ `pictureUrl` | string | Avatar link | +| ↳ `registeredAt` | string | When the user joined the workspace | +| ↳ `roleChangedAt` | string | When the role last changed | +| ↳ `lastActiveAt` | string | Date the user last acted in any workspace | +| ↳ `ownedDocs` | number | Docs the user owns in this workspace | +| ↳ `docsLastActiveAt` | string | Date anyone last accessed a doc the user owns | +| ↳ `docCollaboratorCount` | number | Collaborators on docs the user owns in the last 90 days | +| ↳ `totalDocs` | number | Docs the user owns, manages, or added pages to in the last 90 days | +| ↳ `totalDocsLastActiveAt` | string | Date anyone last accessed a doc the user owns or contributed to | +| ↳ `totalDocCollaboratorsLast90Days` | number | Unique viewers of docs the user owns, manages, or added pages to | + +### Coda List Workspace Role Activity + +Get monthly counts of active and inactive Admins, Doc Makers, and Editors in a workspace. The workspace must belong to an organization. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `workspaceId` | string | Yes | ID of the workspace \(e.g., "ws-1Ab234"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `roleActivity` | array | Role counts per month | +| ↳ `month` | string | Month of the data \(YYYY-MM-DD\) | +| ↳ `activeAdminCount` | number | Active Admins | +| ↳ `activeDocMakerCount` | number | Active Doc Makers | +| ↳ `activeEditorCount` | number | Active Editors | +| ↳ `inactiveAdminCount` | number | Inactive Admins | +| ↳ `inactiveDocMakerCount` | number | Inactive Doc Makers | +| ↳ `inactiveEditorCount` | number | Inactive Editors | + +### Coda Publish Doc + +Publish a Coda doc or update its publishing settings: URL slug, discoverability, categories, and interaction mode. The doc owner needs a Coda maker profile. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `slug` | string | No | URL slug for the published doc \(e.g., "my-doc"\) | +| `discoverable` | boolean | No | Whether the published doc is discoverable in the gallery | +| `categoryNames` | json | No | Category names to apply, as an array or comma-separated list \(see List Doc Categories\) | +| `mode` | string | No | Interaction mode for viewers: "view", "play", or "edit" | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docs` | json | Docs \(id, name, href, browserLink, icon, owner, ownerName, createdAt, updatedAt, workspace, folder, sourceDoc, docSize, published\) | +| `doc` | json | Doc \(id, name, href, browserLink, icon, owner, ownerName, createdAt, updatedAt, workspace \{id, name, organizationId\}, folder \{id, name\}, sourceDoc, docSize \{totalRowCount, tableAndViewCount, baseTableCount, pageCount, overApiSizeLimit\}, published \{description, browserLink, discoverable, mode, categories\}\) | +| `docId` | string | ID of the affected doc | +| `categories` | json | Doc category names | +| `requestId` | string | Request ID of a queued change, for Get Mutation Status | +| `customDomains` | json | Custom domains \(customDocDomain, hasCertificate, hasDnsDocId, setupStatus, domainStatus, lastVerifiedTimestamp\) | +| `customDocDomain` | string | Custom domain | +| `provider` | string | DNS provider of a custom domain | +| `permissions` | json | Permissions \(id, access, principal \{type, email, groupId, groupName, domain, workspaceId\}\) | +| `access` | string | Access level granted | +| `principalType` | string | Type of principal the doc was shared with | +| `permissionId` | string | ID of the removed permission | +| `users` | json | Matching users \(name, loginId, pictureLink\) | +| `groups` | json | Matching groups \(groupId, groupName\) | +| `canShare` | boolean | Whether the user can share the doc | +| `canShareWithWorkspace` | boolean | Whether the user can share with the workspace | +| `canShareWithOrg` | boolean | Whether the user can share with the org | +| `canCopy` | boolean | Whether the user can copy the doc | +| `allowEditorsToChangePermissions` | boolean | Whether editors can change permissions | +| `allowCopying` | boolean | Whether viewers can copy the doc | +| `allowViewersToRequestEditing` | boolean | Whether viewers can request editing | +| `pages` | json | Pages \(id, name, subtitle, browserLink, contentType, isHidden, icon, image, parent, children, authors, createdAt, updatedAt\) | +| `page` | json | Page \(id, name, subtitle, href, browserLink, contentType, isHidden, isEffectivelyHidden, icon, image, parent, children, authors, createdAt, createdBy, updatedAt, updatedBy\) | +| `pageId` | string | ID of the created, updated, or deleted page | +| `items` | json | Page content lines \(id, type, style, format, content, lineLevel\), or analytics items \(doc or page plus daily metrics\) | +| `exportId` | string | Page export ID | +| `status` | string | Page export status \(inProgress, failed, complete\) | +| `href` | string | API link reporting the page export status | +| `downloadLink` | string | Download link of a completed page export | +| `exportError` | string | Error message of a failed page export | +| `tables` | json | Tables and views \(id, name, tableType, href, browserLink, parent\) | +| `table` | json | Table \(id, name, tableType, browserLink, parent, parentTable, displayColumnId, rowCount, sorts, layout, filter, createdAt, updatedAt\) | +| `columns` | json | Columns \(id, name, display, calculated, formula, defaultValue, format\) | +| `column` | json | Column \(id, name, display, calculated, formula, defaultValue, format, parentTable\) | +| `rows` | json | Rows \(id, name, index, browserLink, createdAt, updatedAt, values\) | +| `row` | json | Row \(id, name, index, browserLink, createdAt, updatedAt, values, parentTable\) | +| `nextSyncToken` | string | Token for reading only rows changed later | +| `addedRowIds` | json | IDs of rows that will be added | +| `rowId` | string | ID of the affected row | +| `rowIds` | json | IDs of rows queued for deletion | +| `columnId` | string | ID of the pushed button column | +| `formulas` | json | Named formulas \(id, name, href, parent\) | +| `formula` | json | Formula \(id, name, href, parent, value\) | +| `controls` | json | Controls \(id, name, href, parent\) | +| `control` | json | Control \(id, name, href, parent, controlType, value\) | +| `folders` | json | Folders \(id, name, browserLink, description, icon, iconColor, createdAt, canEdit, workspace\) | +| `folder` | json | Folder \(id, name, browserLink, description, icon, iconColor, createdAt, canEdit, workspace\) | +| `folderId` | string | ID of the deleted folder | +| `children` | json | Subfolders \(id, name, browserLink, visibility, workspace, ...\) | +| `members` | json | Workspace members \(email, name, role, registeredAt, roleChangedAt, lastActiveAt, ownedDocs, totalDocs, ...\) | +| `email` | string | Email of the member whose role changed | +| `newRole` | string | Role assigned | +| `roleChangedAt` | string | When the role change took effect | +| `roleActivity` | json | Monthly role counts \(month, activeAdminCount, activeDocMakerCount, activeEditorCount, inactive counts\) | +| `totalSessions` | number | Total sessions across matching docs | +| `docAnalyticsLastUpdated` | string | Date doc analytics last updated | +| `packAnalyticsLastUpdated` | string | Date Pack analytics last updated | +| `packFormulaAnalyticsLastUpdated` | string | Date Pack formula analytics last updated | +| `browserLink` | string | Canonical browser link of a resolved resource | +| `resource` | json | Resolved resource \(type, id, name, href\) | +| `completed` | boolean | Whether a queued change was applied | +| `warning` | string | Warning for a change that completed with caveats | +| `name` | string | Name of the token owner | +| `loginId` | string | Email of the token owner | +| `pictureLink` | string | Avatar link of the token owner | +| `scoped` | boolean | Whether the token is restricted | +| `tokenName` | string | Name of the API token | +| `workspace` | json | Default workspace of the token owner \(id, name, organizationId, browserLink\) | +| `nextPageToken` | string | Token for fetching the next page of results | + +### Coda Push Button + +Push a button column on a row of a Coda table, running its action. The button can perform any action in the doc. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableId` | string | Yes | ID or name of the table or view \(IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported\) | +| `rowId` | string | Yes | ID or name of the row \(IDs are recommended, e.g., "i-tuVwxYz"; names containing "/" are not supported\) | +| `columnId` | string | Yes | ID or name of the button column \(e.g., "c-tuVwxYz"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `rowId` | string | ID of the row containing the button | +| `columnId` | string | ID of the button column | + +### Coda Resolve Browser Link + +Resolve a Coda browser URL (doc, page, table, row, etc.) into its resource type and ID for use in other Coda operations + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `url` | string | Yes | Coda browser link, e.g., https://coda.io/d/_dAbCDeFGH/Launch-Status_sumnO | +| `degradeGracefully` | boolean | No | If the linked object was deleted, resolve the nearest existing parent \(up to the doc\) instead of failing | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `browserLink` | string | Canonical browser link to the resource | +| `resource` | object | The resolved resource | +| ↳ `type` | string | Resource type \(doc, page, table, row, column, formula, control, etc.\) | +| ↳ `id` | string | Resource ID | +| ↳ `name` | string | Resource name | +| ↳ `href` | string | API link to the resource | + +### Coda Search Principals + +Search for users and groups a Coda doc can be shared with (up to 20 of each). Returns nothing without a query. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `query` | string | No | Name or email to search for | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `users` | array | Matching users | +| ↳ `name` | string | User name | +| ↳ `loginId` | string | User email address | +| ↳ `pictureLink` | string | Avatar link | +| `groups` | array | Matching groups | +| ↳ `groupId` | string | Group ID | +| ↳ `groupName` | string | Group name | + +### Coda Trigger Automation + +Trigger a webhook-invoked automation in a Coda doc, optionally passing a JSON payload the automation can read + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `ruleId` | string | Yes | ID of the automation rule \(e.g., "grid-auto-b3Jmey6jBS"\) | +| `payload` | json | No | JSON object passed to the automation | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docs` | json | Docs \(id, name, href, browserLink, icon, owner, ownerName, createdAt, updatedAt, workspace, folder, sourceDoc, docSize, published\) | +| `doc` | json | Doc \(id, name, href, browserLink, icon, owner, ownerName, createdAt, updatedAt, workspace \{id, name, organizationId\}, folder \{id, name\}, sourceDoc, docSize \{totalRowCount, tableAndViewCount, baseTableCount, pageCount, overApiSizeLimit\}, published \{description, browserLink, discoverable, mode, categories\}\) | +| `docId` | string | ID of the affected doc | +| `categories` | json | Doc category names | +| `requestId` | string | Request ID of a queued change, for Get Mutation Status | +| `customDomains` | json | Custom domains \(customDocDomain, hasCertificate, hasDnsDocId, setupStatus, domainStatus, lastVerifiedTimestamp\) | +| `customDocDomain` | string | Custom domain | +| `provider` | string | DNS provider of a custom domain | +| `permissions` | json | Permissions \(id, access, principal \{type, email, groupId, groupName, domain, workspaceId\}\) | +| `access` | string | Access level granted | +| `principalType` | string | Type of principal the doc was shared with | +| `permissionId` | string | ID of the removed permission | +| `users` | json | Matching users \(name, loginId, pictureLink\) | +| `groups` | json | Matching groups \(groupId, groupName\) | +| `canShare` | boolean | Whether the user can share the doc | +| `canShareWithWorkspace` | boolean | Whether the user can share with the workspace | +| `canShareWithOrg` | boolean | Whether the user can share with the org | +| `canCopy` | boolean | Whether the user can copy the doc | +| `allowEditorsToChangePermissions` | boolean | Whether editors can change permissions | +| `allowCopying` | boolean | Whether viewers can copy the doc | +| `allowViewersToRequestEditing` | boolean | Whether viewers can request editing | +| `pages` | json | Pages \(id, name, subtitle, browserLink, contentType, isHidden, icon, image, parent, children, authors, createdAt, updatedAt\) | +| `page` | json | Page \(id, name, subtitle, href, browserLink, contentType, isHidden, isEffectivelyHidden, icon, image, parent, children, authors, createdAt, createdBy, updatedAt, updatedBy\) | +| `pageId` | string | ID of the created, updated, or deleted page | +| `items` | json | Page content lines \(id, type, style, format, content, lineLevel\), or analytics items \(doc or page plus daily metrics\) | +| `exportId` | string | Page export ID | +| `status` | string | Page export status \(inProgress, failed, complete\) | +| `href` | string | API link reporting the page export status | +| `downloadLink` | string | Download link of a completed page export | +| `exportError` | string | Error message of a failed page export | +| `tables` | json | Tables and views \(id, name, tableType, href, browserLink, parent\) | +| `table` | json | Table \(id, name, tableType, browserLink, parent, parentTable, displayColumnId, rowCount, sorts, layout, filter, createdAt, updatedAt\) | +| `columns` | json | Columns \(id, name, display, calculated, formula, defaultValue, format\) | +| `column` | json | Column \(id, name, display, calculated, formula, defaultValue, format, parentTable\) | +| `rows` | json | Rows \(id, name, index, browserLink, createdAt, updatedAt, values\) | +| `row` | json | Row \(id, name, index, browserLink, createdAt, updatedAt, values, parentTable\) | +| `nextSyncToken` | string | Token for reading only rows changed later | +| `addedRowIds` | json | IDs of rows that will be added | +| `rowId` | string | ID of the affected row | +| `rowIds` | json | IDs of rows queued for deletion | +| `columnId` | string | ID of the pushed button column | +| `formulas` | json | Named formulas \(id, name, href, parent\) | +| `formula` | json | Formula \(id, name, href, parent, value\) | +| `controls` | json | Controls \(id, name, href, parent\) | +| `control` | json | Control \(id, name, href, parent, controlType, value\) | +| `folders` | json | Folders \(id, name, browserLink, description, icon, iconColor, createdAt, canEdit, workspace\) | +| `folder` | json | Folder \(id, name, browserLink, description, icon, iconColor, createdAt, canEdit, workspace\) | +| `folderId` | string | ID of the deleted folder | +| `children` | json | Subfolders \(id, name, browserLink, visibility, workspace, ...\) | +| `members` | json | Workspace members \(email, name, role, registeredAt, roleChangedAt, lastActiveAt, ownedDocs, totalDocs, ...\) | +| `email` | string | Email of the member whose role changed | +| `newRole` | string | Role assigned | +| `roleChangedAt` | string | When the role change took effect | +| `roleActivity` | json | Monthly role counts \(month, activeAdminCount, activeDocMakerCount, activeEditorCount, inactive counts\) | +| `totalSessions` | number | Total sessions across matching docs | +| `docAnalyticsLastUpdated` | string | Date doc analytics last updated | +| `packAnalyticsLastUpdated` | string | Date Pack analytics last updated | +| `packFormulaAnalyticsLastUpdated` | string | Date Pack formula analytics last updated | +| `browserLink` | string | Canonical browser link of a resolved resource | +| `resource` | json | Resolved resource \(type, id, name, href\) | +| `completed` | boolean | Whether a queued change was applied | +| `warning` | string | Warning for a change that completed with caveats | +| `name` | string | Name of the token owner | +| `loginId` | string | Email of the token owner | +| `pictureLink` | string | Avatar link of the token owner | +| `scoped` | boolean | Whether the token is restricted | +| `tokenName` | string | Name of the API token | +| `workspace` | json | Default workspace of the token owner \(id, name, organizationId, browserLink\) | +| `nextPageToken` | string | Token for fetching the next page of results | + +### Coda Unpublish Doc + +Unpublish a Coda doc + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docId` | string | ID of the unpublished doc | + +### Coda Update Sharing Settings + +Update who can change permissions, copy, or request edit access on a Coda doc; unset settings are left unchanged + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `allowEditorsToChangePermissions` | boolean | No | Allow editors to change doc permissions | +| `allowCopying` | boolean | No | Allow viewers to copy the doc | +| `allowViewersToRequestEditing` | boolean | No | Allow viewers to request edit access | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docs` | json | Docs \(id, name, href, browserLink, icon, owner, ownerName, createdAt, updatedAt, workspace, folder, sourceDoc, docSize, published\) | +| `doc` | json | Doc \(id, name, href, browserLink, icon, owner, ownerName, createdAt, updatedAt, workspace \{id, name, organizationId\}, folder \{id, name\}, sourceDoc, docSize \{totalRowCount, tableAndViewCount, baseTableCount, pageCount, overApiSizeLimit\}, published \{description, browserLink, discoverable, mode, categories\}\) | +| `docId` | string | ID of the affected doc | +| `categories` | json | Doc category names | +| `requestId` | string | Request ID of a queued change, for Get Mutation Status | +| `customDomains` | json | Custom domains \(customDocDomain, hasCertificate, hasDnsDocId, setupStatus, domainStatus, lastVerifiedTimestamp\) | +| `customDocDomain` | string | Custom domain | +| `provider` | string | DNS provider of a custom domain | +| `permissions` | json | Permissions \(id, access, principal \{type, email, groupId, groupName, domain, workspaceId\}\) | +| `access` | string | Access level granted | +| `principalType` | string | Type of principal the doc was shared with | +| `permissionId` | string | ID of the removed permission | +| `users` | json | Matching users \(name, loginId, pictureLink\) | +| `groups` | json | Matching groups \(groupId, groupName\) | +| `canShare` | boolean | Whether the user can share the doc | +| `canShareWithWorkspace` | boolean | Whether the user can share with the workspace | +| `canShareWithOrg` | boolean | Whether the user can share with the org | +| `canCopy` | boolean | Whether the user can copy the doc | +| `allowEditorsToChangePermissions` | boolean | Whether editors can change permissions | +| `allowCopying` | boolean | Whether viewers can copy the doc | +| `allowViewersToRequestEditing` | boolean | Whether viewers can request editing | +| `pages` | json | Pages \(id, name, subtitle, browserLink, contentType, isHidden, icon, image, parent, children, authors, createdAt, updatedAt\) | +| `page` | json | Page \(id, name, subtitle, href, browserLink, contentType, isHidden, isEffectivelyHidden, icon, image, parent, children, authors, createdAt, createdBy, updatedAt, updatedBy\) | +| `pageId` | string | ID of the created, updated, or deleted page | +| `items` | json | Page content lines \(id, type, style, format, content, lineLevel\), or analytics items \(doc or page plus daily metrics\) | +| `exportId` | string | Page export ID | +| `status` | string | Page export status \(inProgress, failed, complete\) | +| `href` | string | API link reporting the page export status | +| `downloadLink` | string | Download link of a completed page export | +| `exportError` | string | Error message of a failed page export | +| `tables` | json | Tables and views \(id, name, tableType, href, browserLink, parent\) | +| `table` | json | Table \(id, name, tableType, browserLink, parent, parentTable, displayColumnId, rowCount, sorts, layout, filter, createdAt, updatedAt\) | +| `columns` | json | Columns \(id, name, display, calculated, formula, defaultValue, format\) | +| `column` | json | Column \(id, name, display, calculated, formula, defaultValue, format, parentTable\) | +| `rows` | json | Rows \(id, name, index, browserLink, createdAt, updatedAt, values\) | +| `row` | json | Row \(id, name, index, browserLink, createdAt, updatedAt, values, parentTable\) | +| `nextSyncToken` | string | Token for reading only rows changed later | +| `addedRowIds` | json | IDs of rows that will be added | +| `rowId` | string | ID of the affected row | +| `rowIds` | json | IDs of rows queued for deletion | +| `columnId` | string | ID of the pushed button column | +| `formulas` | json | Named formulas \(id, name, href, parent\) | +| `formula` | json | Formula \(id, name, href, parent, value\) | +| `controls` | json | Controls \(id, name, href, parent\) | +| `control` | json | Control \(id, name, href, parent, controlType, value\) | +| `folders` | json | Folders \(id, name, browserLink, description, icon, iconColor, createdAt, canEdit, workspace\) | +| `folder` | json | Folder \(id, name, browserLink, description, icon, iconColor, createdAt, canEdit, workspace\) | +| `folderId` | string | ID of the deleted folder | +| `children` | json | Subfolders \(id, name, browserLink, visibility, workspace, ...\) | +| `members` | json | Workspace members \(email, name, role, registeredAt, roleChangedAt, lastActiveAt, ownedDocs, totalDocs, ...\) | +| `email` | string | Email of the member whose role changed | +| `newRole` | string | Role assigned | +| `roleChangedAt` | string | When the role change took effect | +| `roleActivity` | json | Monthly role counts \(month, activeAdminCount, activeDocMakerCount, activeEditorCount, inactive counts\) | +| `totalSessions` | number | Total sessions across matching docs | +| `docAnalyticsLastUpdated` | string | Date doc analytics last updated | +| `packAnalyticsLastUpdated` | string | Date Pack analytics last updated | +| `packFormulaAnalyticsLastUpdated` | string | Date Pack formula analytics last updated | +| `browserLink` | string | Canonical browser link of a resolved resource | +| `resource` | json | Resolved resource \(type, id, name, href\) | +| `completed` | boolean | Whether a queued change was applied | +| `warning` | string | Warning for a change that completed with caveats | +| `name` | string | Name of the token owner | +| `loginId` | string | Email of the token owner | +| `pictureLink` | string | Avatar link of the token owner | +| `scoped` | boolean | Whether the token is restricted | +| `tokenName` | string | Name of the API token | +| `workspace` | json | Default workspace of the token owner \(id, name, organizationId, browserLink\) | +| `nextPageToken` | string | Token for fetching the next page of results | + +### Coda Update Doc + +Rename a Coda doc or change its icon. Renaming requires Doc Maker access in the workspace. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `title` | string | No | New title of the doc | +| `iconName` | string | No | Name of the icon to use \(e.g., "rocket"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `docId` | string | ID of the updated doc | + +### Coda Update Folder + +Rename a Coda folder or change its description. Coda can return the folder as it was before the change; read it again to confirm. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `folderId` | string | Yes | ID of the folder \(e.g., "fl-1Ab234"\) | +| `name` | string | No | New name of the folder | +| `description` | string | No | New description of the folder | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `folder` | object | The updated folder | + +### Coda Update Page + +Update a Coda page: rename it, change its subtitle, icon, cover, or visibility, and append, prepend, or replace content with Markdown or HTML. Applied asynchronously. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `pageId` | string | Yes | ID or name of the page \(IDs are recommended, e.g., "canvas-IjkLmnO"; names containing "/" are not supported\) | +| `name` | string | No | New name of the page | +| `subtitle` | string | No | New subtitle of the page \(an empty value leaves the subtitle unchanged\) | +| `iconName` | string | No | Name of the page icon \(e.g., "rocket"\) | +| `imageUrl` | string | No | URL of a cover image for the page | +| `isHidden` | boolean | No | Whether the page is hidden \(requires a paid Coda plan; ignored for pages that cannot be hidden\) | +| `insertionMode` | string | No | How to apply content: "append", "prepend", or "replace". Required when content is provided. | +| `elementId` | string | No | Page element to insert relative to or replace \(e.g., "cl-lzqh0Q0poT"\); omit to apply to the whole page | +| `contentFormat` | string | No | Content format: "markdown" \(default\) or "html" | +| `content` | string | No | Content to add to the page in the chosen format | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `pageId` | string | ID of the updated page | + +### Coda Update Row + +Update cell values in a row of a Coda table. Applied asynchronously. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableId` | string | Yes | ID or name of the table or view \(IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported\) | +| `rowId` | string | Yes | ID or name of the row \(IDs are recommended, e.g., "i-tuVwxYz"; names containing "/" are not supported\) | +| `cells` | json | Yes | Object mapping column IDs \(or names\) to new values, e.g., \{"c-tuVwxYz": "Done"\}, or Coda cells \[\{"column": "c-tuVwxYz", "value": "Done"\}\] | +| `disableParsing` | boolean | No | Store values exactly as given without parsing them | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `rowId` | string | ID of the updated row | + +### Coda Insert or Upsert Rows + +Insert rows into a Coda base table, or update matching rows when key columns are given. Only works on base tables, not views. Applied asynchronously. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `docId` | string | Yes | ID of the doc \(e.g., "AbCDeFGH"\) | +| `tableId` | string | Yes | ID or name of the table or view \(IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported\) | +| `rows` | json | Yes | Array of rows. Each row maps column IDs \(or names\) to values, e.g., \[\{"c-tuVwxYz": "Apple", "c-bCdeFgh": 12\}\], or uses Coda cells \[\{"cells": \[\{"column": "c-tuVwxYz", "value": "Apple"\}\]\}\] | +| `keyColumns` | json | No | Column IDs \(or names\) to match existing rows on, as an array or comma-separated list. Matching rows are updated instead of inserted. | +| `disableParsing` | boolean | No | Store values exactly as given without parsing them | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `addedRowIds` | array | IDs of rows that will be added \(only returned when no key columns are set\) | + +### Coda Get Current User + +Get the user and default workspace behind the connected Coda API token + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `name` | string | Name of the user | +| `loginId` | string | Email address of the user | +| `pictureLink` | string | Link to the user avatar | +| `scoped` | boolean | Whether the token is restricted to specific docs or tables | +| `tokenName` | string | Name of the API token | +| `workspace` | object | Default workspace of the user | + + diff --git a/apps/docs/content/docs/integrations/meta.json b/apps/docs/content/docs/integrations/meta.json index b97450113b3..ad8e80316f1 100644 --- a/apps/docs/content/docs/integrations/meta.json +++ b/apps/docs/content/docs/integrations/meta.json @@ -46,6 +46,7 @@ "cloudformation", "cloudtrail", "cloudwatch", + "coda", "codepipeline", "confluence", "context_dev", diff --git a/apps/docs/openapi-v2-workflows.json b/apps/docs/openapi-v2-workflows.json index c92eacd9b5e..5b7c250dca8 100644 --- a/apps/docs/openapi-v2-workflows.json +++ b/apps/docs/openapi-v2-workflows.json @@ -15854,6 +15854,15 @@ "clickup.spaces", "clickup.folders", "clickup.lists", + "coda.docs", + "coda.pages", + "coda.tables", + "coda.columns", + "coda.rows", + "coda.formulas", + "coda.controls", + "coda.folders", + "coda.permissions", "confluence.spaces", "confluence.spacesById", "confluence.pages", @@ -16020,6 +16029,15 @@ "clickup.spaces", "clickup.folders", "clickup.lists", + "coda.docs", + "coda.pages", + "coda.tables", + "coda.columns", + "coda.rows", + "coda.formulas", + "coda.controls", + "coda.folders", + "coda.permissions", "confluence.spaces", "confluence.spacesById", "confluence.pages", diff --git a/apps/sim/blocks/blocks/coda.ts b/apps/sim/blocks/blocks/coda.ts new file mode 100644 index 00000000000..2b57ba5fa2c --- /dev/null +++ b/apps/sim/blocks/blocks/coda.ts @@ -0,0 +1,1995 @@ +import { CodaIcon } from '@/components/icons' +import { AuthMode, type BlockConfig, type BlockMeta, IntegrationType } from '@/blocks/types' +import type { CodaResponse } from '@/tools/coda/types' + +/** Canonical credential pair: the credential picker in basic mode, a raw credential id in advanced. */ +const CREDENTIAL_FIELD = ['credential', 'manualCredential'] +const DOC_FIELD = ['docSelector', 'manualDocId'] +const PAGE_FIELD = ['pageSelector', 'manualPageId'] +const TABLE_FIELD = ['tableSelector', 'manualTableId'] +const ROW_FIELD = ['rowSelector', 'manualRowId'] +const COLUMN_FIELD = ['columnSelector', 'manualColumnId'] +const FORMULA_FIELD = ['formulaSelector', 'manualFormulaId'] +const CONTROL_FIELD = ['controlSelector', 'manualControlId'] +const FOLDER_FIELD = ['folderSelector', 'manualFolderId'] +const PERMISSION_FIELD = ['permissionSelector', 'manualPermissionId'] + +const PAGE_OPERATIONS = [ + 'get_page', + 'update_page', + 'delete_page', + 'get_page_content', + 'delete_page_content', + 'export_page', + 'get_page_export_status', +] + +const ROW_OPERATIONS = ['get_row', 'update_row', 'delete_row', 'push_button'] + +const TABLE_OPERATIONS = [ + 'get_table', + 'list_columns', + 'get_column', + 'list_rows', + 'upsert_rows', + 'delete_rows', + ...ROW_OPERATIONS, +] + +const DOC_OPERATIONS = [ + 'get_doc', + 'update_doc', + 'delete_doc', + 'publish_doc', + 'unpublish_doc', + 'list_custom_domains', + 'add_custom_domain', + 'delete_custom_domain', + 'list_permissions', + 'add_permission', + 'delete_permission', + 'search_principals', + 'get_sharing_metadata', + 'get_acl_settings', + 'update_acl_settings', + 'list_pages', + 'create_page', + ...PAGE_OPERATIONS, + 'list_tables', + ...TABLE_OPERATIONS, + 'list_formulas', + 'get_formula', + 'list_controls', + 'get_control', + 'trigger_automation', + 'list_page_analytics', +] + +const FOLDER_REQUIRED_OPERATIONS = [ + 'get_folder', + 'update_folder', + 'delete_folder', + 'list_folder_children', +] +const FOLDER_OPERATIONS = ['list_docs', 'create_doc', ...FOLDER_REQUIRED_OPERATIONS] + +const WORKSPACE_REQUIRED_OPERATIONS = [ + 'create_folder', + 'list_workspace_members', + 'change_user_role', + 'list_workspace_roles', +] +const WORKSPACE_FILTER_OPERATIONS = [ + 'list_docs', + 'list_folders', + 'list_doc_analytics', + 'get_doc_analytics_summary', +] + +const PAGE_CREATE_OPERATIONS = ['create_doc', 'create_page'] +const PAGE_WRITE_OPERATIONS = [...PAGE_CREATE_OPERATIONS, 'update_page'] +const ANALYTICS_DATE_OPERATIONS = [ + 'list_doc_analytics', + 'list_page_analytics', + 'get_doc_analytics_summary', +] + +const LIMIT_OPERATIONS = [ + 'list_docs', + 'list_pages', + 'get_page_content', + 'list_tables', + 'list_columns', + 'list_rows', + 'list_formulas', + 'list_controls', + 'list_folders', + 'list_folder_children', + 'list_permissions', + 'list_doc_analytics', + 'list_page_analytics', +] +const PAGE_TOKEN_OPERATIONS = [...LIMIT_OPERATIONS, 'list_workspace_members'] + +const LIST_SORT_OPERATIONS = ['list_tables', 'list_formulas', 'list_controls'] + +/** Switches only send `true`; an off switch leaves the Coda filter unset. */ +function trueOrUndefined(value: unknown): true | undefined { + return value === true || value === 'true' ? true : undefined +} + +/** Maps a three-state dropdown (unchanged / true / false) to an optional boolean. */ +function triState(value: unknown): boolean | undefined { + if (value === 'true' || value === true) return true + if (value === 'false' || value === false) return false + return undefined +} + +function optionalNumber(value: unknown): number | undefined { + if (value === undefined || value === null || value === '') return undefined + const parsed = Number(value) + return Number.isFinite(parsed) ? parsed : undefined +} + +function unlessDefault(value: unknown, defaultValue: string): string | undefined { + return typeof value === 'string' && value !== '' && value !== defaultValue ? value : undefined +} + +/** Content fields apply to every page update, but only to canvas pages when creating. */ +function canvasContentCondition(values?: Record) { + return values?.operation === 'update_page' + ? { field: 'operation', value: 'update_page' } + : { + field: 'operation', + value: PAGE_CREATE_OPERATIONS, + and: { field: 'pageType', value: 'canvas' }, + } +} + +const TRI_STATE_OPTIONS = [ + { label: 'Unchanged', id: 'unchanged' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, +] + +const DATE_WAND = { + enabled: true, + prompt: + 'Generate a date in YYYY-MM-DD format based on the user description. Return ONLY the date string.', + placeholder: 'Describe the date (e.g., "30 days ago")...', + generationType: 'timestamp', +} as const + +export const CodaBlock: BlockConfig = { + type: 'coda', + name: 'Coda', + description: 'Read and write Coda docs, pages, tables, and rows', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrate Coda (Superhuman Docs) into your workflow with a reusable API-token credential. Create, copy, publish, and share docs; create, update, read, export, and clear pages; read table schemas; list, insert, upsert, update, and delete rows and push row buttons; read formulas and controls; trigger webhook automations; manage folders, custom domains, and workspace roles; and read doc and page analytics. Pick docs, pages, tables, rows, and more from dropdowns populated by your account.', + docsLink: 'https://docs.sim.ai/integrations/coda', + category: 'tools', + integrationType: IntegrationType.Documents, + bgColor: '#FFFFFF', + icon: CodaIcon, + canvasPresentation: { + defaultTitle: 'Coda', + sentences: { + byOperation: { + list_docs: ['List docs', { text: 'matching', field: 'docSearch' }], + get_doc: [{ text: 'Read doc', field: DOC_FIELD, core: true }], + create_doc: ['Create doc', { text: 'titled', field: 'docTitle' }], + update_doc: [ + { text: 'Update doc', field: DOC_FIELD, core: true }, + { text: 'to', field: 'docTitle' }, + ], + delete_doc: [{ text: 'Delete doc', field: DOC_FIELD, core: true }], + publish_doc: [{ text: 'Publish', field: DOC_FIELD, core: true }], + unpublish_doc: [{ text: 'Unpublish', field: DOC_FIELD, core: true }], + list_categories: ['List doc categories'], + list_custom_domains: [{ text: 'List custom domains of', field: DOC_FIELD, core: true }], + add_custom_domain: [ + { text: 'Add domain', field: 'customDocDomain', core: true }, + { text: 'to', field: DOC_FIELD, core: true }, + ], + delete_custom_domain: [ + { text: 'Remove domain', field: 'customDocDomain', core: true }, + { text: 'from', field: DOC_FIELD, core: true }, + ], + get_custom_domain_provider: [ + { text: 'Look up DNS provider of', field: 'customDocDomain', core: true }, + ], + list_permissions: [{ text: 'List permissions on', field: DOC_FIELD, core: true }], + add_permission: [ + { text: 'Share', field: DOC_FIELD, core: true }, + { text: 'with', field: 'principal' }, + { text: 'as', field: 'access' }, + ], + delete_permission: [ + { text: 'Remove permission', field: PERMISSION_FIELD, core: true }, + { text: 'from', field: DOC_FIELD }, + ], + search_principals: [ + { text: 'Search users and groups for', field: DOC_FIELD, core: true }, + { text: 'matching', field: 'principalQuery' }, + ], + get_sharing_metadata: [{ text: 'Check sharing rights on', field: DOC_FIELD, core: true }], + get_acl_settings: [{ text: 'Read sharing settings of', field: DOC_FIELD, core: true }], + update_acl_settings: [{ text: 'Update sharing settings of', field: DOC_FIELD, core: true }], + list_pages: [{ text: 'List pages in', field: DOC_FIELD, core: true }], + get_page: [ + { text: 'Read page', field: PAGE_FIELD, core: true }, + { text: 'in', field: DOC_FIELD }, + ], + create_page: [ + 'Create page', + { text: 'named', field: 'pageName' }, + { text: 'in', field: DOC_FIELD, core: true }, + ], + update_page: [ + { text: 'Update page', field: PAGE_FIELD, core: true }, + { text: 'in', field: DOC_FIELD }, + ], + delete_page: [ + { text: 'Delete page', field: PAGE_FIELD, core: true }, + { text: 'from', field: DOC_FIELD }, + ], + get_page_content: [{ text: 'Read content of', field: PAGE_FIELD, core: true }], + delete_page_content: [{ text: 'Delete content from', field: PAGE_FIELD, core: true }], + export_page: [ + { text: 'Export', field: PAGE_FIELD, core: true }, + { text: 'as', field: 'outputFormat' }, + ], + get_page_export_status: [{ text: 'Check export', field: 'exportId', core: true }], + list_tables: [{ text: 'List tables in', field: DOC_FIELD, core: true }], + get_table: [{ text: 'Read table', field: TABLE_FIELD, core: true }], + list_columns: [{ text: 'List columns of', field: TABLE_FIELD, core: true }], + get_column: [ + { text: 'Read column', field: COLUMN_FIELD, core: true }, + { text: 'of', field: TABLE_FIELD }, + ], + list_rows: [ + { text: 'List rows in', field: TABLE_FIELD, core: true }, + { text: 'where', field: 'rowFilter' }, + ], + get_row: [ + { text: 'Read row', field: ROW_FIELD, core: true }, + { text: 'from', field: TABLE_FIELD }, + ], + upsert_rows: [{ text: 'Insert or update rows in', field: TABLE_FIELD, core: true }], + update_row: [ + { text: 'Update row', field: ROW_FIELD, core: true }, + { text: 'in', field: TABLE_FIELD }, + ], + delete_row: [ + { text: 'Delete row', field: ROW_FIELD, core: true }, + { text: 'from', field: TABLE_FIELD }, + ], + delete_rows: [ + { text: 'Delete rows', field: 'rowIds', core: true }, + { text: 'from', field: TABLE_FIELD }, + ], + push_button: [ + { text: 'Push button', field: COLUMN_FIELD, core: true }, + { text: 'on row', field: ROW_FIELD }, + ], + list_formulas: [{ text: 'List formulas in', field: DOC_FIELD, core: true }], + get_formula: [{ text: 'Read formula', field: FORMULA_FIELD, core: true }], + list_controls: [{ text: 'List controls in', field: DOC_FIELD, core: true }], + get_control: [{ text: 'Read control', field: CONTROL_FIELD, core: true }], + trigger_automation: [ + { text: 'Trigger automation', field: 'ruleId', core: true }, + { text: 'in', field: DOC_FIELD }, + ], + list_folders: ['List folders'], + get_folder: [{ text: 'Read folder', field: FOLDER_FIELD, core: true }], + create_folder: [ + { text: 'Create folder', field: 'folderName', core: true }, + { text: 'in', field: 'workspaceId' }, + ], + update_folder: [{ text: 'Update folder', field: FOLDER_FIELD, core: true }], + delete_folder: [{ text: 'Delete folder', field: FOLDER_FIELD, core: true }], + list_folder_children: [{ text: 'List subfolders of', field: FOLDER_FIELD, core: true }], + list_workspace_members: [{ text: 'List members of', field: 'workspaceId', core: true }], + change_user_role: [ + { text: 'Set role of', field: 'memberEmail', core: true }, + { text: 'to', field: 'newRole' }, + ], + list_workspace_roles: [{ text: 'Read role activity of', field: 'workspaceId', core: true }], + list_doc_analytics: ['Read doc analytics', { text: 'since', field: 'sinceDate' }], + list_page_analytics: [{ text: 'Read page analytics of', field: DOC_FIELD, core: true }], + get_doc_analytics_summary: [ + 'Read total doc sessions', + { text: 'since', field: 'sinceDate' }, + ], + get_analytics_last_updated: ['Check when analytics last updated'], + resolve_browser_link: [{ text: 'Resolve', field: 'browserUrl', core: true }], + get_mutation_status: [{ text: 'Check status of', field: 'mutationRequestId', core: true }], + whoami: ['Read current user'], + }, + }, + }, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Docs', id: 'list_docs' }, + { label: 'Get Doc', id: 'get_doc' }, + { label: 'Create Doc', id: 'create_doc' }, + { label: 'Update Doc', id: 'update_doc' }, + { label: 'Delete Doc', id: 'delete_doc' }, + { label: 'Publish Doc', id: 'publish_doc' }, + { label: 'Unpublish Doc', id: 'unpublish_doc' }, + { label: 'List Doc Categories', id: 'list_categories' }, + { label: 'List Custom Domains', id: 'list_custom_domains' }, + { label: 'Add Custom Domain', id: 'add_custom_domain' }, + { label: 'Delete Custom Domain', id: 'delete_custom_domain' }, + { label: 'Get Custom Domain Provider', id: 'get_custom_domain_provider' }, + { label: 'List Permissions', id: 'list_permissions' }, + { label: 'Share Doc', id: 'add_permission' }, + { label: 'Remove Permission', id: 'delete_permission' }, + { label: 'Search Users and Groups', id: 'search_principals' }, + { label: 'Get Sharing Metadata', id: 'get_sharing_metadata' }, + { label: 'Get Sharing Settings', id: 'get_acl_settings' }, + { label: 'Update Sharing Settings', id: 'update_acl_settings' }, + { label: 'List Pages', id: 'list_pages' }, + { label: 'Get Page', id: 'get_page' }, + { label: 'Create Page', id: 'create_page' }, + { label: 'Update Page', id: 'update_page' }, + { label: 'Delete Page', id: 'delete_page' }, + { label: 'Get Page Content', id: 'get_page_content' }, + { label: 'Delete Page Content', id: 'delete_page_content' }, + { label: 'Export Page', id: 'export_page' }, + { label: 'Get Page Export Status', id: 'get_page_export_status' }, + { label: 'List Tables', id: 'list_tables' }, + { label: 'Get Table', id: 'get_table' }, + { label: 'List Columns', id: 'list_columns' }, + { label: 'Get Column', id: 'get_column' }, + { label: 'List Rows', id: 'list_rows' }, + { label: 'Get Row', id: 'get_row' }, + { label: 'Insert or Upsert Rows', id: 'upsert_rows' }, + { label: 'Update Row', id: 'update_row' }, + { label: 'Delete Row', id: 'delete_row' }, + { label: 'Delete Rows', id: 'delete_rows' }, + { label: 'Push Button', id: 'push_button' }, + { label: 'List Formulas', id: 'list_formulas' }, + { label: 'Get Formula', id: 'get_formula' }, + { label: 'List Controls', id: 'list_controls' }, + { label: 'Get Control', id: 'get_control' }, + { label: 'Trigger Automation', id: 'trigger_automation' }, + { label: 'List Folders', id: 'list_folders' }, + { label: 'Get Folder', id: 'get_folder' }, + { label: 'Create Folder', id: 'create_folder' }, + { label: 'Update Folder', id: 'update_folder' }, + { label: 'Delete Folder', id: 'delete_folder' }, + { label: 'List Subfolders', id: 'list_folder_children' }, + { label: 'List Workspace Members', id: 'list_workspace_members' }, + { label: 'Change User Role', id: 'change_user_role' }, + { label: 'List Workspace Role Activity', id: 'list_workspace_roles' }, + { label: 'List Doc Analytics', id: 'list_doc_analytics' }, + { label: 'List Page Analytics', id: 'list_page_analytics' }, + { label: 'Get Doc Analytics Summary', id: 'get_doc_analytics_summary' }, + { label: 'Get Analytics Last Updated', id: 'get_analytics_last_updated' }, + { label: 'Resolve Browser Link', id: 'resolve_browser_link' }, + { label: 'Get Mutation Status', id: 'get_mutation_status' }, + { label: 'Get Current User', id: 'whoami' }, + ], + value: () => 'list_rows', + }, + { + id: 'credential', + title: 'Coda Account', + type: 'oauth-input', + serviceId: 'coda', + credentialKind: 'service-account', + canonicalParamId: 'oauthCredential', + mode: 'basic', + placeholder: 'Select Coda credential', + required: true, + }, + { + id: 'manualCredential', + title: 'Coda Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + }, + { + id: 'docSelector', + title: 'Doc', + canvasNoun: 'a doc', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.docs', + canonicalParamId: 'docId', + placeholder: 'Select a doc', + dependsOn: ['credential'], + mode: 'basic', + condition: { field: 'operation', value: DOC_OPERATIONS }, + required: { field: 'operation', value: DOC_OPERATIONS }, + }, + { + id: 'manualDocId', + title: 'Doc ID', + canvasNoun: 'a doc', + type: 'short-input', + canonicalParamId: 'docId', + placeholder: 'e.g., AbCDeFGH (from the doc URL after _d)', + mode: 'advanced', + condition: { field: 'operation', value: DOC_OPERATIONS }, + required: { field: 'operation', value: DOC_OPERATIONS }, + }, + { + id: 'pageSelector', + title: 'Page', + canvasNoun: 'a page', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.pages', + canonicalParamId: 'pageId', + placeholder: 'Select a page', + dependsOn: ['credential', 'docSelector'], + mode: 'basic', + condition: { field: 'operation', value: PAGE_OPERATIONS }, + required: { field: 'operation', value: PAGE_OPERATIONS }, + }, + { + id: 'manualPageId', + title: 'Page ID or Name', + canvasNoun: 'a page', + type: 'short-input', + canonicalParamId: 'pageId', + placeholder: 'e.g., canvas-IjkLmnO', + mode: 'advanced', + condition: { field: 'operation', value: PAGE_OPERATIONS }, + required: { field: 'operation', value: PAGE_OPERATIONS }, + }, + { + id: 'tableSelector', + title: 'Table', + canvasNoun: 'a table', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.tables', + canonicalParamId: 'tableId', + placeholder: 'Select a table (inserts need a base table, not a view)', + dependsOn: ['credential', 'docSelector'], + mode: 'basic', + condition: { field: 'operation', value: TABLE_OPERATIONS }, + required: { field: 'operation', value: TABLE_OPERATIONS }, + }, + { + id: 'manualTableId', + title: 'Table ID or Name', + canvasNoun: 'a table', + type: 'short-input', + canonicalParamId: 'tableId', + placeholder: 'e.g., grid-pqRst-U', + mode: 'advanced', + condition: { field: 'operation', value: TABLE_OPERATIONS }, + required: { field: 'operation', value: TABLE_OPERATIONS }, + }, + { + id: 'rowSelector', + title: 'Row', + canvasNoun: 'a row', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.rows', + canonicalParamId: 'rowId', + placeholder: 'Select a row', + dependsOn: ['credential', 'docSelector', 'tableSelector'], + mode: 'basic', + condition: { field: 'operation', value: ROW_OPERATIONS }, + required: { field: 'operation', value: ROW_OPERATIONS }, + }, + { + id: 'manualRowId', + title: 'Row ID or Name', + canvasNoun: 'a row', + type: 'short-input', + canonicalParamId: 'rowId', + placeholder: 'e.g., i-tuVwxYz', + mode: 'advanced', + condition: { field: 'operation', value: ROW_OPERATIONS }, + required: { field: 'operation', value: ROW_OPERATIONS }, + }, + { + id: 'columnSelector', + title: 'Column', + canvasNoun: 'a column', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.columns', + canonicalParamId: 'columnId', + placeholder: 'Select a column', + dependsOn: ['credential', 'docSelector', 'tableSelector'], + mode: 'basic', + condition: { field: 'operation', value: ['get_column', 'push_button'] }, + required: { field: 'operation', value: ['get_column', 'push_button'] }, + }, + { + id: 'manualColumnId', + title: 'Column ID or Name', + canvasNoun: 'a column', + type: 'short-input', + canonicalParamId: 'columnId', + placeholder: 'e.g., c-tuVwxYz', + mode: 'advanced', + condition: { field: 'operation', value: ['get_column', 'push_button'] }, + required: { field: 'operation', value: ['get_column', 'push_button'] }, + }, + { + id: 'formulaSelector', + title: 'Formula', + canvasNoun: 'a formula', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.formulas', + canonicalParamId: 'formulaId', + placeholder: 'Select a named formula', + dependsOn: ['credential', 'docSelector'], + mode: 'basic', + condition: { field: 'operation', value: 'get_formula' }, + required: { field: 'operation', value: 'get_formula' }, + }, + { + id: 'manualFormulaId', + title: 'Formula ID or Name', + canvasNoun: 'a formula', + type: 'short-input', + canonicalParamId: 'formulaId', + placeholder: 'e.g., f-fgHijkLm', + mode: 'advanced', + condition: { field: 'operation', value: 'get_formula' }, + required: { field: 'operation', value: 'get_formula' }, + }, + { + id: 'controlSelector', + title: 'Control', + canvasNoun: 'a control', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.controls', + canonicalParamId: 'controlId', + placeholder: 'Select a control', + dependsOn: ['credential', 'docSelector'], + mode: 'basic', + condition: { field: 'operation', value: 'get_control' }, + required: { field: 'operation', value: 'get_control' }, + }, + { + id: 'manualControlId', + title: 'Control ID or Name', + canvasNoun: 'a control', + type: 'short-input', + canonicalParamId: 'controlId', + placeholder: 'e.g., ctrl-cDefGhij', + mode: 'advanced', + condition: { field: 'operation', value: 'get_control' }, + required: { field: 'operation', value: 'get_control' }, + }, + { + id: 'permissionSelector', + title: 'Permission', + canvasNoun: 'a permission', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.permissions', + canonicalParamId: 'permissionId', + placeholder: 'Select who to remove', + dependsOn: ['credential', 'docSelector'], + mode: 'basic', + condition: { field: 'operation', value: 'delete_permission' }, + required: { field: 'operation', value: 'delete_permission' }, + }, + { + id: 'manualPermissionId', + title: 'Permission ID', + canvasNoun: 'a permission', + type: 'short-input', + canonicalParamId: 'permissionId', + placeholder: 'Permission ID from List Permissions', + mode: 'advanced', + condition: { field: 'operation', value: 'delete_permission' }, + required: { field: 'operation', value: 'delete_permission' }, + }, + { + id: 'folderSelector', + title: 'Folder', + canvasNoun: 'a folder', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.folders', + canonicalParamId: 'folderId', + placeholder: 'Select a folder', + dependsOn: ['credential'], + mode: 'basic', + condition: { field: 'operation', value: FOLDER_OPERATIONS }, + required: { field: 'operation', value: FOLDER_REQUIRED_OPERATIONS }, + }, + { + id: 'manualFolderId', + title: 'Folder ID', + canvasNoun: 'a folder', + type: 'short-input', + canonicalParamId: 'folderId', + placeholder: 'e.g., fl-1Ab234', + mode: 'advanced', + condition: { field: 'operation', value: FOLDER_OPERATIONS }, + required: { field: 'operation', value: FOLDER_REQUIRED_OPERATIONS }, + }, + { + id: 'workspaceId', + title: 'Workspace ID', + canvasNoun: 'a workspace', + type: 'short-input', + placeholder: 'e.g., ws-1Ab234 (see Get Current User)', + condition: { field: 'operation', value: WORKSPACE_REQUIRED_OPERATIONS }, + required: { field: 'operation', value: WORKSPACE_REQUIRED_OPERATIONS }, + }, + { + id: 'docSearch', + title: 'Search', + type: 'short-input', + placeholder: 'Filter docs by name', + condition: { field: 'operation', value: ['list_docs', 'list_doc_analytics'] }, + }, + { + id: 'docTitle', + title: 'Doc Title', + type: 'short-input', + placeholder: 'e.g., Project Tracker', + condition: { field: 'operation', value: ['create_doc', 'update_doc'] }, + }, + { + id: 'sourceDocSelector', + title: 'Copy From Doc', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.docs', + canonicalParamId: 'sourceDoc', + placeholder: 'Select a doc to copy', + dependsOn: ['credential'], + mode: 'basic', + condition: { field: 'operation', value: ['create_doc', 'list_docs'] }, + }, + { + id: 'manualSourceDoc', + title: 'Copy From Doc ID', + type: 'short-input', + canonicalParamId: 'sourceDoc', + placeholder: 'ID of the source doc', + mode: 'advanced', + condition: { field: 'operation', value: ['create_doc', 'list_docs'] }, + }, + { + id: 'pageName', + title: 'Page Name', + type: 'short-input', + placeholder: 'e.g., Launch Status', + condition: { field: 'operation', value: PAGE_WRITE_OPERATIONS }, + }, + { + id: 'parentPageSelector', + title: 'Parent Page', + type: 'project-selector', + serviceId: 'coda', + selectorKey: 'coda.pages', + canonicalParamId: 'parentPageId', + placeholder: 'Create as a subpage of…', + dependsOn: ['credential', 'docSelector'], + mode: 'basic', + condition: { field: 'operation', value: 'create_page' }, + }, + { + id: 'manualParentPageId', + title: 'Parent Page ID', + type: 'short-input', + canonicalParamId: 'parentPageId', + placeholder: 'e.g., canvas-tuVwxYz', + mode: 'advanced', + condition: { field: 'operation', value: 'create_page' }, + }, + { + id: 'pageType', + title: 'Page Type', + type: 'dropdown', + options: [ + { label: 'Content (Markdown or HTML)', id: 'canvas' }, + { label: 'Full-page Embed', id: 'embed' }, + { label: 'Sync Page', id: 'syncPage' }, + ], + value: () => 'canvas', + condition: { field: 'operation', value: PAGE_CREATE_OPERATIONS }, + }, + { + id: 'insertionMode', + title: 'Content Mode', + type: 'dropdown', + options: [ + { label: 'Append', id: 'append' }, + { label: 'Prepend', id: 'prepend' }, + { label: 'Replace', id: 'replace' }, + ], + value: () => 'append', + condition: { field: 'operation', value: 'update_page' }, + }, + { + id: 'contentFormat', + title: 'Content Format', + type: 'dropdown', + options: [ + { label: 'Markdown', id: 'markdown' }, + { label: 'HTML', id: 'html' }, + ], + value: () => 'markdown', + condition: canvasContentCondition, + }, + { + id: 'pageContent', + title: 'Content', + type: 'long-input', + placeholder: '# Heading\n\nPage content in the selected format', + rows: 8, + condition: canvasContentCondition, + wandConfig: { + enabled: true, + prompt: + 'Write Coda page content in Markdown based on the user request. Use headings, lists, and short paragraphs. Return ONLY the Markdown content.', + placeholder: 'Describe the page content...', + }, + }, + { + id: 'embedUrl', + title: 'Embed URL', + type: 'short-input', + placeholder: 'https://example.com', + condition: { + field: 'operation', + value: PAGE_CREATE_OPERATIONS, + and: { field: 'pageType', value: 'embed' }, + }, + required: { + field: 'operation', + value: PAGE_CREATE_OPERATIONS, + and: { field: 'pageType', value: 'embed' }, + }, + }, + { + id: 'renderMethod', + title: 'Embed Render Method', + type: 'dropdown', + options: [ + { label: 'Standard', id: 'standard' }, + { label: 'Compatibility', id: 'compatibility' }, + ], + value: () => 'standard', + condition: { + field: 'operation', + value: PAGE_CREATE_OPERATIONS, + and: { field: 'pageType', value: 'embed' }, + }, + mode: 'advanced', + }, + { + id: 'syncSourceDocId', + title: 'Sync From Doc ID', + type: 'short-input', + placeholder: 'ID of the doc to sync from', + condition: { + field: 'operation', + value: PAGE_CREATE_OPERATIONS, + and: { field: 'pageType', value: 'syncPage' }, + }, + required: { + field: 'operation', + value: PAGE_CREATE_OPERATIONS, + and: { field: 'pageType', value: 'syncPage' }, + }, + }, + { + id: 'syncMode', + title: 'Sync', + type: 'dropdown', + options: [ + { label: 'A Single Page', id: 'page' }, + { label: 'The Whole Doc', id: 'document' }, + ], + value: () => 'page', + condition: { + field: 'operation', + value: PAGE_CREATE_OPERATIONS, + and: { field: 'pageType', value: 'syncPage' }, + }, + }, + { + id: 'syncSourcePageId', + title: 'Sync Page ID', + type: 'short-input', + placeholder: 'ID of the page to sync', + required: true, + condition: (values) => + values?.syncMode === 'document' + ? { field: 'syncMode', value: 'page' } + : { + field: 'operation', + value: PAGE_CREATE_OPERATIONS, + and: { field: 'pageType', value: 'syncPage' }, + }, + }, + { + id: 'includeSubpages', + title: 'Include Subpages', + type: 'switch', + condition: { + field: 'operation', + value: PAGE_CREATE_OPERATIONS, + and: { field: 'pageType', value: 'syncPage' }, + }, + mode: 'advanced', + }, + { + id: 'elementIds', + title: 'Element IDs', + type: 'short-input', + placeholder: 'Comma-separated element IDs from Get Page Content', + condition: { field: 'operation', value: 'delete_page_content' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a comma-separated list of Coda page element IDs (like cl-lzqh0Q0poT) from the user description. Return ONLY the comma-separated list.', + placeholder: 'Describe which elements to delete...', + }, + }, + { + id: 'deleteAllContent', + title: 'Delete All Page Content', + type: 'switch', + condition: { field: 'operation', value: 'delete_page_content' }, + }, + { + id: 'outputFormat', + title: 'Export Format', + type: 'dropdown', + options: [ + { label: 'Markdown', id: 'markdown' }, + { label: 'HTML', id: 'html' }, + ], + required: true, + value: () => 'markdown', + condition: { field: 'operation', value: 'export_page' }, + }, + { + id: 'exportId', + title: 'Export ID', + canvasNoun: 'an export', + type: 'short-input', + placeholder: 'exportId from Export Page', + condition: { field: 'operation', value: 'get_page_export_status' }, + required: { field: 'operation', value: 'get_page_export_status' }, + }, + { + id: 'rowFilter', + title: 'Filter', + type: 'short-input', + placeholder: 'e.g., c-tuVwxYz:"Done" or "Status":"Done"', + condition: { field: 'operation', value: 'list_rows' }, + wandConfig: { + enabled: true, + prompt: `Generate a Coda row filter from the user's description. +The format is :. Column names must be quoted, and string values must be quoted JSON strings. +Examples: c-tuVwxYz:"Apple" "Status":"Done" "Priority":3 "Done":true +Return ONLY the filter.`, + placeholder: 'Describe which rows to return...', + }, + }, + { + id: 'useColumnNames', + title: 'Use Column Names', + type: 'switch', + condition: { field: 'operation', value: ['list_rows', 'get_row'] }, + }, + { + id: 'rows', + title: 'Rows', + type: 'code', + language: 'json', + placeholder: '[{ "Name": "Apple", "Price": 1.25 }]', + condition: { field: 'operation', value: 'upsert_rows' }, + required: { field: 'operation', value: 'upsert_rows' }, + wandConfig: { + enabled: true, + prompt: `Generate a JSON array of rows for a Coda table based on the user's description. +Each row is an object mapping column IDs (like "c-tuVwxYz") or column names to cell values. +Example: [{"Name": "Apple", "Price": 1.25}, {"Name": "Pear", "Price": 2}] +Return ONLY the JSON array.`, + placeholder: 'Describe the rows to add...', + generationType: 'json-object', + }, + }, + { + id: 'keyColumns', + title: 'Upsert Key Columns', + type: 'short-input', + placeholder: 'Comma-separated column IDs or names, e.g., c-tuVwxYz', + condition: { field: 'operation', value: 'upsert_rows' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a comma-separated list of Coda column IDs (like c-tuVwxYz) or column names to match existing rows on, from the user description. Return ONLY the comma-separated list.', + placeholder: 'Describe the columns that identify a row...', + }, + }, + { + id: 'cells', + title: 'Cell Values', + type: 'code', + language: 'json', + placeholder: '{ "Status": "Done" }', + condition: { field: 'operation', value: 'update_row' }, + required: { field: 'operation', value: 'update_row' }, + wandConfig: { + enabled: true, + prompt: `Generate a JSON object of Coda cell updates based on the user's description. +Keys are column IDs (like "c-tuVwxYz") or column names; values are the new cell values. +Example: {"Status": "Done", "Owner": "alex@example.com"} +Return ONLY the JSON object.`, + placeholder: 'Describe the changes...', + generationType: 'json-object', + }, + }, + { + id: 'rowIds', + title: 'Row IDs', + canvasNoun: 'rows', + type: 'short-input', + placeholder: 'Comma-separated row IDs, e.g., i-bCdeFgh, i-CdEfgHi', + condition: { field: 'operation', value: 'delete_rows' }, + required: { field: 'operation', value: 'delete_rows' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a comma-separated list of Coda row IDs (like i-bCdeFgh) from the user description. Return ONLY the comma-separated list.', + placeholder: 'Describe the rows to delete...', + }, + }, + { + id: 'ruleId', + title: 'Automation Rule ID', + canvasNoun: 'an automation', + type: 'short-input', + placeholder: 'e.g., grid-auto-b3Jmey6jBS', + condition: { field: 'operation', value: 'trigger_automation' }, + required: { field: 'operation', value: 'trigger_automation' }, + }, + { + id: 'payload', + title: 'Payload', + type: 'code', + language: 'json', + placeholder: '{ "message": "Hello from Sim" }', + condition: { field: 'operation', value: 'trigger_automation' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON object payload for a Coda webhook automation based on the user description. Return ONLY the JSON object.', + placeholder: 'Describe the payload...', + generationType: 'json-object', + }, + }, + { + id: 'access', + title: 'Access', + type: 'dropdown', + options: [ + { label: 'Can View', id: 'readonly' }, + { label: 'Can Comment', id: 'comment' }, + { label: 'Can Edit', id: 'write' }, + ], + required: true, + value: () => 'readonly', + condition: { field: 'operation', value: 'add_permission' }, + }, + { + id: 'principalType', + title: 'Share With', + type: 'dropdown', + options: [ + { label: 'Email', id: 'email' }, + { label: 'Group', id: 'group' }, + { label: 'Domain', id: 'domain' }, + { label: 'Workspace', id: 'workspace' }, + { label: 'Anyone With the Link', id: 'anyone' }, + ], + required: true, + value: () => 'email', + condition: { field: 'operation', value: 'add_permission' }, + }, + { + id: 'principal', + title: 'Email, Group ID, Domain, or Workspace ID', + canvasNoun: 'someone', + type: 'short-input', + placeholder: 'e.g., teammate@example.com', + condition: { + field: 'operation', + value: 'add_permission', + and: { field: 'principalType', value: 'anyone', not: true }, + }, + required: { + field: 'operation', + value: 'add_permission', + and: { field: 'principalType', value: 'anyone', not: true }, + }, + }, + { + id: 'principalQuery', + title: 'Search', + type: 'short-input', + placeholder: 'Name or email', + condition: { field: 'operation', value: 'search_principals' }, + }, + { + id: 'slug', + title: 'URL Slug', + type: 'short-input', + placeholder: 'e.g., my-doc', + condition: { field: 'operation', value: 'publish_doc' }, + }, + { + id: 'publishMode', + title: 'Viewer Mode', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: 'unchanged' }, + { label: 'View Only', id: 'view' }, + { label: 'Play (Interact Without Saving)', id: 'play' }, + { label: 'Edit', id: 'edit' }, + ], + value: () => 'unchanged', + condition: { field: 'operation', value: 'publish_doc' }, + }, + { + id: 'discoverable', + title: 'Discoverable', + type: 'dropdown', + options: TRI_STATE_OPTIONS, + value: () => 'unchanged', + condition: { field: 'operation', value: 'publish_doc' }, + mode: 'advanced', + }, + { + id: 'categoryNames', + title: 'Categories', + type: 'short-input', + placeholder: 'Comma-separated names from List Doc Categories', + condition: { field: 'operation', value: 'publish_doc' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a comma-separated list of Coda doc category names (like Project management) from the user description. Return ONLY the comma-separated list.', + placeholder: 'Describe the categories...', + }, + }, + { + id: 'allowEditorsToChangePermissions', + title: 'Editors Can Change Permissions', + type: 'dropdown', + options: TRI_STATE_OPTIONS, + value: () => 'unchanged', + condition: { field: 'operation', value: 'update_acl_settings' }, + }, + { + id: 'allowCopying', + title: 'Viewers Can Copy', + type: 'dropdown', + options: TRI_STATE_OPTIONS, + value: () => 'unchanged', + condition: { field: 'operation', value: 'update_acl_settings' }, + }, + { + id: 'allowViewersToRequestEditing', + title: 'Viewers Can Request Editing', + type: 'dropdown', + options: TRI_STATE_OPTIONS, + value: () => 'unchanged', + condition: { field: 'operation', value: 'update_acl_settings' }, + }, + { + id: 'customDocDomain', + title: 'Custom Domain', + canvasNoun: 'a domain', + type: 'short-input', + placeholder: 'e.g., docs.example.com', + condition: { + field: 'operation', + value: ['add_custom_domain', 'delete_custom_domain', 'get_custom_domain_provider'], + }, + required: { + field: 'operation', + value: ['add_custom_domain', 'delete_custom_domain', 'get_custom_domain_provider'], + }, + }, + { + id: 'folderName', + title: 'Folder Name', + canvasNoun: 'a folder', + type: 'short-input', + placeholder: 'e.g., Projects', + condition: { field: 'operation', value: ['create_folder', 'update_folder'] }, + required: { field: 'operation', value: 'create_folder' }, + }, + { + id: 'folderDescription', + title: 'Folder Description', + type: 'long-input', + placeholder: 'What the folder contains', + condition: { field: 'operation', value: ['create_folder', 'update_folder'] }, + }, + { + id: 'memberEmail', + title: 'Member Email', + canvasNoun: 'a member', + type: 'short-input', + placeholder: 'teammate@example.com', + condition: { field: 'operation', value: 'change_user_role' }, + required: { field: 'operation', value: 'change_user_role' }, + }, + { + id: 'newRole', + title: 'New Role', + type: 'dropdown', + options: [ + { label: 'Doc Maker', id: 'DocMaker' }, + { label: 'Editor', id: 'Editor' }, + { label: 'Admin', id: 'Admin' }, + ], + required: true, + value: () => 'DocMaker', + condition: { field: 'operation', value: 'change_user_role' }, + }, + { + id: 'browserUrl', + title: 'Coda URL', + canvasNoun: 'a link', + type: 'short-input', + placeholder: 'https://coda.io/d/_dAbCDeFGH/Launch-Status_sumnO', + condition: { field: 'operation', value: 'resolve_browser_link' }, + required: { field: 'operation', value: 'resolve_browser_link' }, + }, + { + id: 'mutationRequestId', + title: 'Request ID', + canvasNoun: 'a change', + type: 'short-input', + placeholder: 'requestId returned by a write operation', + condition: { field: 'operation', value: 'get_mutation_status' }, + required: { field: 'operation', value: 'get_mutation_status' }, + }, + { + id: 'workspaceFilter', + title: 'Workspace ID', + type: 'short-input', + placeholder: 'Only include this workspace, e.g., ws-1Ab234', + condition: { field: 'operation', value: WORKSPACE_FILTER_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'isOwner', + title: 'Only Docs I Own', + type: 'switch', + condition: { field: 'operation', value: 'list_docs' }, + mode: 'advanced', + }, + { + id: 'isPublished', + title: 'Only Published Docs', + type: 'switch', + condition: { + field: 'operation', + value: ['list_docs', 'list_doc_analytics', 'get_doc_analytics_summary'], + }, + mode: 'advanced', + }, + { + id: 'starred', + title: 'Starred', + type: 'dropdown', + options: [ + { label: 'Any', id: 'any' }, + { label: 'Starred Only', id: 'true' }, + { label: 'Not Starred', id: 'false' }, + ], + value: () => 'any', + condition: { field: 'operation', value: ['list_docs', 'list_folders'] }, + mode: 'advanced', + }, + { + id: 'inGallery', + title: 'Only Gallery Docs', + type: 'switch', + condition: { field: 'operation', value: 'list_docs' }, + mode: 'advanced', + }, + { + id: 'timezone', + title: 'Timezone', + type: 'short-input', + placeholder: 'e.g., America/Los_Angeles', + condition: { field: 'operation', value: 'create_doc' }, + mode: 'advanced', + }, + { + id: 'pageSubtitle', + title: 'Page Subtitle', + type: 'short-input', + condition: { field: 'operation', value: PAGE_WRITE_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'iconName', + title: 'Icon Name', + type: 'short-input', + placeholder: 'e.g., rocket', + condition: { field: 'operation', value: ['update_doc', ...PAGE_WRITE_OPERATIONS] }, + mode: 'advanced', + }, + { + id: 'imageUrl', + title: 'Cover Image URL', + type: 'short-input', + placeholder: 'https://example.com/image.jpg', + condition: { field: 'operation', value: PAGE_WRITE_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'elementId', + title: 'Anchor Element ID', + type: 'short-input', + placeholder: 'e.g., cl-lzqh0Q0poT (from Get Page Content)', + condition: { field: 'operation', value: 'update_page' }, + mode: 'advanced', + }, + { + id: 'pageVisibility', + title: 'Page Visibility', + type: 'dropdown', + options: [ + { label: 'Unchanged', id: 'unchanged' }, + { label: 'Visible', id: 'visible' }, + { label: 'Hidden', id: 'hidden' }, + ], + value: () => 'unchanged', + condition: { field: 'operation', value: 'update_page' }, + mode: 'advanced', + }, + { + id: 'tableTypes', + title: 'Table Types', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'Tables Only', id: 'table' }, + { label: 'Views Only', id: 'view' }, + { label: 'Databases Only', id: 'database' }, + ], + value: () => 'all', + condition: { field: 'operation', value: 'list_tables' }, + mode: 'advanced', + }, + { + id: 'listSortBy', + title: 'Sort By', + type: 'dropdown', + options: [ + { label: 'Default', id: 'default' }, + { label: 'Name', id: 'name' }, + ], + value: () => 'default', + condition: { field: 'operation', value: LIST_SORT_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'useUpdatedTableLayouts', + title: 'Distinguish Detail and Form Layouts', + type: 'switch', + condition: { field: 'operation', value: 'get_table' }, + mode: 'advanced', + }, + { + id: 'rowSortBy', + title: 'Sort By', + type: 'dropdown', + options: [ + { label: 'Created At', id: 'createdAt' }, + { label: 'Updated At', id: 'updatedAt' }, + { label: 'View Order', id: 'natural' }, + ], + value: () => 'createdAt', + condition: { field: 'operation', value: 'list_rows' }, + mode: 'advanced', + }, + { + id: 'valueFormat', + title: 'Value Format', + type: 'dropdown', + options: [ + { label: 'Simple', id: 'simple' }, + { label: 'Simple With Arrays', id: 'simpleWithArrays' }, + { label: 'Rich', id: 'rich' }, + ], + value: () => 'simple', + condition: { field: 'operation', value: ['list_rows', 'get_row'] }, + mode: 'advanced', + }, + { + id: 'visibleOnly', + title: 'Visible Only', + type: 'switch', + condition: { field: 'operation', value: ['list_rows', 'list_columns'] }, + mode: 'advanced', + }, + { + id: 'syncToken', + title: 'Sync Token', + type: 'short-input', + placeholder: 'nextSyncToken from a previous List Rows', + condition: { field: 'operation', value: 'list_rows' }, + mode: 'advanced', + }, + { + id: 'disableParsing', + title: 'Disable Value Parsing', + type: 'switch', + condition: { field: 'operation', value: ['upsert_rows', 'update_row'] }, + mode: 'advanced', + }, + { + id: 'suppressEmail', + title: 'Skip Notification Email', + type: 'switch', + condition: { field: 'operation', value: 'add_permission' }, + mode: 'advanced', + }, + { + id: 'degradeGracefully', + title: 'Resolve Nearest Parent If Deleted', + type: 'switch', + condition: { field: 'operation', value: 'resolve_browser_link' }, + mode: 'advanced', + }, + { + id: 'includedRoles', + title: 'Roles', + type: 'short-input', + placeholder: 'Comma-separated: Admin, DocMaker, Editor', + condition: { field: 'operation', value: 'list_workspace_members' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a comma-separated list of Coda workspace roles using only Admin, DocMaker, and Editor, from the user description. Return ONLY the comma-separated list.', + placeholder: 'Describe which roles to include...', + }, + }, + { + id: 'docIds', + title: 'Doc IDs', + type: 'short-input', + placeholder: 'Comma-separated doc IDs', + condition: { field: 'operation', value: 'list_doc_analytics' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate a comma-separated list of Coda doc IDs (like AbCDeFGH) from the user description. Return ONLY the comma-separated list.', + placeholder: 'Describe the docs...', + }, + }, + { + id: 'sinceDate', + title: 'Since Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: ANALYTICS_DATE_OPERATIONS }, + mode: 'advanced', + wandConfig: DATE_WAND, + }, + { + id: 'untilDate', + title: 'Until Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: ANALYTICS_DATE_OPERATIONS }, + mode: 'advanced', + wandConfig: DATE_WAND, + }, + { + id: 'analyticsScale', + title: 'Scale', + type: 'dropdown', + options: [ + { label: 'Daily', id: 'daily' }, + { label: 'Cumulative', id: 'cumulative' }, + ], + value: () => 'daily', + condition: { field: 'operation', value: 'list_doc_analytics' }, + mode: 'advanced', + }, + { + id: 'analyticsOrderBy', + title: 'Order By', + type: 'dropdown', + options: [ + { label: 'Default', id: 'default' }, + { label: 'Date', id: 'date' }, + { label: 'Title', id: 'title' }, + { label: 'Views', id: 'views' }, + { label: 'Total Sessions', id: 'totalSessions' }, + { label: 'Copies', id: 'copies' }, + { label: 'Likes', id: 'likes' }, + { label: 'AI Credits', id: 'aiCredits' }, + { label: 'Created At', id: 'createdAt' }, + { label: 'Published At', id: 'publishedAt' }, + ], + value: () => 'default', + condition: { field: 'operation', value: 'list_doc_analytics' }, + mode: 'advanced', + }, + { + id: 'analyticsDirection', + title: 'Direction', + type: 'dropdown', + options: [ + { label: 'Default', id: 'default' }, + { label: 'Descending', id: 'descending' }, + { label: 'Ascending', id: 'ascending' }, + ], + value: () => 'default', + condition: { field: 'operation', value: 'list_doc_analytics' }, + mode: 'advanced', + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: 'Maximum results per page', + condition: { field: 'operation', value: LIMIT_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'pageToken', + title: 'Page Token', + type: 'short-input', + placeholder: 'nextPageToken from a previous call', + condition: { field: 'operation', value: PAGE_TOKEN_OPERATIONS }, + mode: 'advanced', + }, + ], + tools: { + access: [ + 'coda_add_custom_domain', + 'coda_add_permission', + 'coda_change_user_role', + 'coda_create_doc', + 'coda_create_folder', + 'coda_create_page', + 'coda_delete_custom_domain', + 'coda_delete_doc', + 'coda_delete_folder', + 'coda_delete_page', + 'coda_delete_page_content', + 'coda_delete_permission', + 'coda_delete_row', + 'coda_delete_rows', + 'coda_export_page', + 'coda_get_acl_settings', + 'coda_get_analytics_last_updated', + 'coda_get_column', + 'coda_get_control', + 'coda_get_custom_domain_provider', + 'coda_get_doc', + 'coda_get_doc_analytics_summary', + 'coda_get_folder', + 'coda_get_formula', + 'coda_get_mutation_status', + 'coda_get_page', + 'coda_get_page_content', + 'coda_get_page_export_status', + 'coda_get_row', + 'coda_get_sharing_metadata', + 'coda_get_table', + 'coda_list_categories', + 'coda_list_columns', + 'coda_list_controls', + 'coda_list_custom_domains', + 'coda_list_doc_analytics', + 'coda_list_docs', + 'coda_list_folder_children', + 'coda_list_folders', + 'coda_list_formulas', + 'coda_list_page_analytics', + 'coda_list_pages', + 'coda_list_permissions', + 'coda_list_rows', + 'coda_list_tables', + 'coda_list_workspace_members', + 'coda_list_workspace_roles', + 'coda_publish_doc', + 'coda_push_button', + 'coda_resolve_browser_link', + 'coda_search_principals', + 'coda_trigger_automation', + 'coda_unpublish_doc', + 'coda_update_acl_settings', + 'coda_update_doc', + 'coda_update_folder', + 'coda_update_page', + 'coda_update_row', + 'coda_upsert_rows', + 'coda_whoami', + ], + config: { + tool: (params) => `coda_${params.operation || 'list_rows'}`, + params: (params) => { + const operation = params.operation + const isFolderWrite = operation === 'create_folder' || operation === 'update_folder' + const pageVisibility = params.pageVisibility + return { + query: + operation === 'list_rows' + ? params.rowFilter + : operation === 'search_principals' + ? params.principalQuery + : params.docSearch, + title: params.docTitle, + name: isFolderWrite + ? params.folderName + : operation === 'create_doc' + ? undefined + : params.pageName, + description: isFolderWrite ? params.folderDescription : undefined, + subtitle: operation === 'create_doc' ? undefined : params.pageSubtitle, + content: params.pageContent, + sourceDocId: params.syncSourceDocId, + sourcePageId: params.syncSourcePageId, + includeSubpages: trueOrUndefined(params.includeSubpages), + isHidden: + pageVisibility === 'hidden' ? true : pageVisibility === 'visible' ? false : undefined, + workspaceId: WORKSPACE_REQUIRED_OPERATIONS.includes(operation) + ? params.workspaceId + : params.workspaceFilter, + email: params.memberEmail, + requestId: params.mutationRequestId, + url: params.browserUrl, + sortBy: + operation === 'list_rows' + ? params.rowSortBy + : params.listSortBy === 'name' + ? 'name' + : undefined, + tableTypes: unlessDefault(params.tableTypes, 'all'), + mode: unlessDefault(params.publishMode, 'unchanged'), + discoverable: triState(params.discoverable), + allowEditorsToChangePermissions: triState(params.allowEditorsToChangePermissions), + allowCopying: triState(params.allowCopying), + allowViewersToRequestEditing: triState(params.allowViewersToRequestEditing), + isStarred: triState(params.starred), + scale: unlessDefault(params.analyticsScale, 'daily'), + orderBy: unlessDefault(params.analyticsOrderBy, 'default'), + direction: unlessDefault(params.analyticsDirection, 'default'), + limit: optionalNumber(params.limit), + deleteAll: trueOrUndefined(params.deleteAllContent), + isOwner: trueOrUndefined(params.isOwner), + isPublished: trueOrUndefined(params.isPublished), + inGallery: trueOrUndefined(params.inGallery), + useColumnNames: trueOrUndefined(params.useColumnNames), + useUpdatedTableLayouts: trueOrUndefined(params.useUpdatedTableLayouts), + visibleOnly: trueOrUndefined(params.visibleOnly), + disableParsing: trueOrUndefined(params.disableParsing), + suppressEmail: trueOrUndefined(params.suppressEmail), + degradeGracefully: trueOrUndefined(params.degradeGracefully), + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + oauthCredential: { type: 'string', description: 'Coda API-token credential' }, + docId: { type: 'string', description: 'Doc ID' }, + pageId: { type: 'string', description: 'Page ID or name' }, + tableId: { type: 'string', description: 'Table or view ID or name' }, + rowId: { type: 'string', description: 'Row ID or name' }, + columnId: { type: 'string', description: 'Column ID or name' }, + formulaId: { type: 'string', description: 'Formula ID or name' }, + controlId: { type: 'string', description: 'Control ID or name' }, + permissionId: { type: 'string', description: 'Permission ID' }, + folderId: { type: 'string', description: 'Folder ID' }, + workspaceId: { type: 'string', description: 'Workspace ID' }, + docSearch: { type: 'string', description: 'Search term to filter docs' }, + docTitle: { type: 'string', description: 'Doc title' }, + sourceDoc: { type: 'string', description: 'Doc ID to copy, or to filter copies of' }, + pageName: { type: 'string', description: 'Page name' }, + parentPageId: { type: 'string', description: 'Parent page ID for a subpage' }, + pageType: { type: 'string', description: 'canvas, embed, or syncPage' }, + insertionMode: { type: 'string', description: 'append, prepend, or replace' }, + contentFormat: { type: 'string', description: 'markdown or html' }, + pageContent: { type: 'string', description: 'Page content' }, + embedUrl: { type: 'string', description: 'URL to embed as a page' }, + renderMethod: { type: 'string', description: 'standard or compatibility embed rendering' }, + syncSourceDocId: { type: 'string', description: 'Doc to sync a page from' }, + syncMode: { type: 'string', description: 'page or document sync' }, + syncSourcePageId: { type: 'string', description: 'Page to sync' }, + includeSubpages: { type: 'boolean', description: 'Include subpages in a sync page' }, + elementIds: { type: 'string', description: 'Page element IDs to delete' }, + deleteAllContent: { type: 'boolean', description: 'Delete all content from the page' }, + outputFormat: { type: 'string', description: 'Export format (markdown or html)' }, + exportId: { type: 'string', description: 'Page export ID' }, + rowFilter: { type: 'string', description: 'Row filter as column:value' }, + useColumnNames: { type: 'boolean', description: 'Key values by column name' }, + rows: { type: 'json', description: 'Rows to insert or upsert' }, + keyColumns: { type: 'string', description: 'Upsert key columns' }, + cells: { type: 'json', description: 'Cell values to update' }, + rowIds: { type: 'string', description: 'Row IDs to delete' }, + ruleId: { type: 'string', description: 'Automation rule ID' }, + payload: { type: 'json', description: 'Automation payload' }, + access: { type: 'string', description: 'Access level to grant' }, + principalType: { type: 'string', description: 'Type of principal to share with' }, + principal: { type: 'string', description: 'Email, group ID, domain, or workspace ID' }, + principalQuery: { type: 'string', description: 'User or group search term' }, + slug: { type: 'string', description: 'Published doc URL slug' }, + publishMode: { type: 'string', description: 'Published doc viewer mode' }, + discoverable: { type: 'string', description: 'Published doc discoverability' }, + categoryNames: { type: 'string', description: 'Published doc categories' }, + allowEditorsToChangePermissions: { + type: 'string', + description: 'Whether editors can change permissions', + }, + allowCopying: { type: 'string', description: 'Whether viewers can copy' }, + allowViewersToRequestEditing: { + type: 'string', + description: 'Whether viewers can request editing', + }, + customDocDomain: { type: 'string', description: 'Custom domain' }, + folderName: { type: 'string', description: 'Folder name' }, + folderDescription: { type: 'string', description: 'Folder description' }, + memberEmail: { type: 'string', description: 'Workspace member email' }, + newRole: { type: 'string', description: 'New workspace role' }, + browserUrl: { type: 'string', description: 'Coda browser link to resolve' }, + mutationRequestId: { type: 'string', description: 'Request ID of a write operation' }, + workspaceFilter: { type: 'string', description: 'Workspace ID filter' }, + isOwner: { type: 'boolean', description: 'Only docs owned by the user' }, + isPublished: { type: 'boolean', description: 'Only published docs' }, + starred: { type: 'string', description: 'Starred filter' }, + inGallery: { type: 'boolean', description: 'Only gallery docs' }, + timezone: { type: 'string', description: 'Timezone for a new doc' }, + pageSubtitle: { type: 'string', description: 'Page subtitle' }, + iconName: { type: 'string', description: 'Icon name' }, + imageUrl: { type: 'string', description: 'Cover image URL' }, + elementId: { type: 'string', description: 'Page element to insert relative to' }, + pageVisibility: { type: 'string', description: 'unchanged, visible, or hidden' }, + tableTypes: { type: 'string', description: 'Table types to list' }, + listSortBy: { type: 'string', description: 'Sort order for tables, formulas, controls' }, + useUpdatedTableLayouts: { type: 'boolean', description: 'Report detail and form layouts' }, + rowSortBy: { type: 'string', description: 'Row sort order' }, + valueFormat: { type: 'string', description: 'Cell value format' }, + visibleOnly: { type: 'boolean', description: 'Only visible rows or columns' }, + syncToken: { type: 'string', description: 'Sync token for incremental row reads' }, + disableParsing: { type: 'boolean', description: 'Store values without parsing' }, + suppressEmail: { type: 'boolean', description: 'Skip the sharing notification email' }, + degradeGracefully: { type: 'boolean', description: 'Resolve nearest parent if deleted' }, + includedRoles: { type: 'string', description: 'Workspace roles to include' }, + docIds: { type: 'string', description: 'Doc IDs for analytics' }, + sinceDate: { type: 'string', description: 'Analytics start date' }, + untilDate: { type: 'string', description: 'Analytics end date' }, + analyticsScale: { type: 'string', description: 'daily or cumulative analytics' }, + analyticsOrderBy: { type: 'string', description: 'Analytics sort field' }, + analyticsDirection: { type: 'string', description: 'Analytics sort direction' }, + limit: { type: 'number', description: 'Maximum results per page' }, + pageToken: { type: 'string', description: 'Pagination token' }, + }, + outputs: { + docs: { + type: 'json', + description: + 'Docs (id, name, href, browserLink, icon, owner, ownerName, createdAt, updatedAt, workspace, folder, sourceDoc, docSize, published)', + }, + doc: { + type: 'json', + description: + 'Doc (id, name, href, browserLink, icon, owner, ownerName, createdAt, updatedAt, workspace {id, name, organizationId}, folder {id, name}, sourceDoc, docSize {totalRowCount, tableAndViewCount, baseTableCount, pageCount, overApiSizeLimit}, published {description, browserLink, discoverable, mode, categories})', + }, + docId: { type: 'string', description: 'ID of the affected doc' }, + categories: { type: 'json', description: 'Doc category names' }, + requestId: { + type: 'string', + description: 'Request ID of a queued change, for Get Mutation Status', + }, + customDomains: { + type: 'json', + description: + 'Custom domains (customDocDomain, hasCertificate, hasDnsDocId, setupStatus, domainStatus, lastVerifiedTimestamp)', + }, + customDocDomain: { type: 'string', description: 'Custom domain' }, + provider: { type: 'string', description: 'DNS provider of a custom domain' }, + permissions: { + type: 'json', + description: + 'Permissions (id, access, principal {type, email, groupId, groupName, domain, workspaceId})', + }, + access: { type: 'string', description: 'Access level granted' }, + principalType: { type: 'string', description: 'Type of principal the doc was shared with' }, + permissionId: { type: 'string', description: 'ID of the removed permission' }, + users: { type: 'json', description: 'Matching users (name, loginId, pictureLink)' }, + groups: { type: 'json', description: 'Matching groups (groupId, groupName)' }, + canShare: { type: 'boolean', description: 'Whether the user can share the doc' }, + canShareWithWorkspace: { + type: 'boolean', + description: 'Whether the user can share with the workspace', + }, + canShareWithOrg: { type: 'boolean', description: 'Whether the user can share with the org' }, + canCopy: { type: 'boolean', description: 'Whether the user can copy the doc' }, + allowEditorsToChangePermissions: { + type: 'boolean', + description: 'Whether editors can change permissions', + }, + allowCopying: { type: 'boolean', description: 'Whether viewers can copy the doc' }, + allowViewersToRequestEditing: { + type: 'boolean', + description: 'Whether viewers can request editing', + }, + pages: { + type: 'json', + description: + 'Pages (id, name, subtitle, browserLink, contentType, isHidden, icon, image, parent, children, authors, createdAt, updatedAt)', + }, + page: { + type: 'json', + description: + 'Page (id, name, subtitle, href, browserLink, contentType, isHidden, isEffectivelyHidden, icon, image, parent, children, authors, createdAt, createdBy, updatedAt, updatedBy)', + }, + pageId: { type: 'string', description: 'ID of the created, updated, or deleted page' }, + items: { + type: 'json', + description: + 'Page content lines (id, type, style, format, content, lineLevel), or analytics items (doc or page plus daily metrics)', + }, + exportId: { type: 'string', description: 'Page export ID' }, + status: { type: 'string', description: 'Page export status (inProgress, failed, complete)' }, + href: { type: 'string', description: 'API link reporting the page export status' }, + downloadLink: { type: 'string', description: 'Download link of a completed page export' }, + exportError: { type: 'string', description: 'Error message of a failed page export' }, + tables: { + type: 'json', + description: 'Tables and views (id, name, tableType, href, browserLink, parent)', + }, + table: { + type: 'json', + description: + 'Table (id, name, tableType, browserLink, parent, parentTable, displayColumnId, rowCount, sorts, layout, filter, createdAt, updatedAt)', + }, + columns: { + type: 'json', + description: 'Columns (id, name, display, calculated, formula, defaultValue, format)', + }, + column: { + type: 'json', + description: + 'Column (id, name, display, calculated, formula, defaultValue, format, parentTable)', + }, + rows: { + type: 'json', + description: 'Rows (id, name, index, browserLink, createdAt, updatedAt, values)', + }, + row: { + type: 'json', + description: 'Row (id, name, index, browserLink, createdAt, updatedAt, values, parentTable)', + }, + nextSyncToken: { type: 'string', description: 'Token for reading only rows changed later' }, + addedRowIds: { type: 'json', description: 'IDs of rows that will be added' }, + rowId: { type: 'string', description: 'ID of the affected row' }, + rowIds: { type: 'json', description: 'IDs of rows queued for deletion' }, + columnId: { type: 'string', description: 'ID of the pushed button column' }, + formulas: { type: 'json', description: 'Named formulas (id, name, href, parent)' }, + formula: { type: 'json', description: 'Formula (id, name, href, parent, value)' }, + controls: { type: 'json', description: 'Controls (id, name, href, parent)' }, + control: { + type: 'json', + description: 'Control (id, name, href, parent, controlType, value)', + }, + folders: { + type: 'json', + description: + 'Folders (id, name, browserLink, description, icon, iconColor, createdAt, canEdit, workspace)', + }, + folder: { + type: 'json', + description: + 'Folder (id, name, browserLink, description, icon, iconColor, createdAt, canEdit, workspace)', + }, + folderId: { type: 'string', description: 'ID of the deleted folder' }, + children: { + type: 'json', + description: 'Subfolders (id, name, browserLink, visibility, workspace, ...)', + }, + members: { + type: 'json', + description: + 'Workspace members (email, name, role, registeredAt, roleChangedAt, lastActiveAt, ownedDocs, totalDocs, ...)', + }, + email: { type: 'string', description: 'Email of the member whose role changed' }, + newRole: { type: 'string', description: 'Role assigned' }, + roleChangedAt: { type: 'string', description: 'When the role change took effect' }, + roleActivity: { + type: 'json', + description: + 'Monthly role counts (month, activeAdminCount, activeDocMakerCount, activeEditorCount, inactive counts)', + }, + totalSessions: { type: 'number', description: 'Total sessions across matching docs' }, + docAnalyticsLastUpdated: { type: 'string', description: 'Date doc analytics last updated' }, + packAnalyticsLastUpdated: { + type: 'string', + description: 'Date Pack analytics last updated', + }, + packFormulaAnalyticsLastUpdated: { + type: 'string', + description: 'Date Pack formula analytics last updated', + }, + browserLink: { type: 'string', description: 'Canonical browser link of a resolved resource' }, + resource: { type: 'json', description: 'Resolved resource (type, id, name, href)' }, + completed: { type: 'boolean', description: 'Whether a queued change was applied' }, + warning: { type: 'string', description: 'Warning for a change that completed with caveats' }, + name: { type: 'string', description: 'Name of the token owner' }, + loginId: { type: 'string', description: 'Email of the token owner' }, + pictureLink: { type: 'string', description: 'Avatar link of the token owner' }, + scoped: { type: 'boolean', description: 'Whether the token is restricted' }, + tokenName: { type: 'string', description: 'Name of the API token' }, + workspace: { + type: 'json', + description: 'Default workspace of the token owner (id, name, organizationId, browserLink)', + }, + nextPageToken: { type: 'string', description: 'Token for fetching the next page of results' }, + }, +} + +export const CodaBlockMeta = { + tags: ['note-taking', 'knowledge-base', 'spreadsheet', 'project-management', 'automation'], + url: 'https://coda.io', + templates: [ + { + icon: CodaIcon, + title: 'Coda meeting notes publisher', + prompt: + 'Build a workflow that takes a meeting transcript, has an agent write a summary with decisions and action items, creates a Coda page for the meeting in the team doc, and inserts each action item as a row in the Coda tasks table with owner and due date.', + modules: ['agent', 'workflows'], + category: 'productivity', + tags: ['meetings', 'automation'], + featured: true, + }, + { + icon: CodaIcon, + title: 'Coda OKR progress report', + prompt: + 'Create a scheduled workflow that reads the OKR table in a Coda doc every Friday, computes progress per objective from the key result rows, and appends a weekly progress summary to the OKR review page in Markdown.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['reporting', 'planning'], + }, + { + icon: CodaIcon, + title: 'Coda CRM lead upsert', + prompt: + 'Build a workflow that receives new leads from a form webhook, has an agent normalize company and contact fields, and upserts each lead into a Coda CRM table keyed on email so repeat submissions update the existing row.', + modules: ['agent', 'workflows'], + category: 'sales', + tags: ['crm', 'sync'], + }, + { + icon: CodaIcon, + title: 'Coda bug tracker triage', + prompt: + 'Create a workflow that lists new rows in a Coda bug tracker table, has an agent classify severity and suggest an owner for each bug, updates the rows with the triage fields, and posts the high-severity bugs to Slack.', + modules: ['agent', 'workflows'], + category: 'engineering', + tags: ['triage', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: CodaIcon, + title: 'Coda wiki to knowledge base', + prompt: + 'Build a scheduled workflow that lists the pages of a Coda team wiki doc, reads each page as text, and syncs the content into a Sim knowledge base so agents can answer questions from the wiki.', + modules: ['scheduled', 'knowledge-base', 'workflows'], + category: 'productivity', + tags: ['knowledge', 'sync'], + }, + { + icon: CodaIcon, + title: 'Coda table to Sim table mirror', + prompt: + 'Create a scheduled workflow that reads rows changed since the last run from a Coda table using its sync token and mirrors them into a Sim table for reporting and downstream agent runs.', + modules: ['scheduled', 'tables', 'workflows'], + category: 'operations', + tags: ['sync', 'data'], + }, + { + icon: CodaIcon, + title: 'Coda project status digest', + prompt: + 'Build a scheduled workflow that reads the project tracker table in a Coda doc each morning, has an agent flag overdue and blocked projects, and emails a status digest to project leads with links to each row.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['reporting', 'project-management'], + alsoIntegrations: ['gmail'], + }, + { + icon: CodaIcon, + title: 'Coda doc access audit', + prompt: + 'Create a scheduled workflow that lists the Coda docs in a workspace, reads the sharing permissions and sharing settings on each, and writes any docs shared with anyone or outside domains to an audit table for review.', + modules: ['scheduled', 'tables', 'agent', 'workflows'], + category: 'operations', + tags: ['security', 'audit'], + }, + { + icon: CodaIcon, + title: 'Coda doc engagement report', + prompt: + 'Build a scheduled workflow that pulls last week of Coda doc analytics, ranks docs by sessions and views, and posts the most and least used team docs to Slack so owners can archive stale docs.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['analytics', 'reporting'], + alsoIntegrations: ['slack'], + }, + ], + skills: [ + { + name: 'log-action-items-to-coda', + description: + 'Turn meeting notes or a conversation into action items and add them as rows in a Coda tasks table.', + content: + '# Log Action Items to Coda\n\nCapture follow-ups from a meeting or thread in a Coda tasks table.\n\n## Steps\n1. Extract each action item with its owner, due date, and a one-line description.\n2. List the columns of the target Coda table to get the exact column IDs or names.\n3. Insert one row per action item, mapping each field to its column. Use the task name as an upsert key when re-running on the same notes.\n4. Keep the returned request ID and check the mutation status if you need to confirm the rows were written.\n\n## Output\nThe number of rows added and the list of action items with their owners.', + }, + { + name: 'answer-from-coda-table', + description: + 'Answer a question using the rows of a Coda table, filtering by a column value when possible.', + content: + '# Answer From Coda Table\n\nUse a Coda table as the source of truth for a question.\n\n## Steps\n1. If only a Coda link is given, resolve the browser link to get the doc and table IDs.\n2. List the table columns to find the columns relevant to the question.\n3. List rows with column names enabled, filtering on a single column value when the question names one.\n4. Page through results with the page token until you have the rows you need.\n5. Answer only from the returned values.\n\n## Output\nA concise answer citing the row names and links used. Say so if no matching rows exist.', + }, + { + name: 'write-status-update-page', + description: + 'Draft a status update and prepend it to a Coda page as Markdown, keeping earlier updates below.', + content: + '# Write Status Update Page\n\nPublish a dated status update into an existing Coda page.\n\n## Steps\n1. Gather the facts for the update (progress, risks, next steps) from the provided sources.\n2. Write the update in Markdown with a dated heading and short bullet lists.\n3. Update the Coda page with content mode set to prepend so the newest update is at the top.\n4. Confirm the change with the returned request ID if needed.\n\n## Output\nThe Markdown that was added and the page link.', + }, + { + name: 'triage-coda-tracker-rows', + description: + 'Review new rows in a Coda tracker table and update their status, priority, or owner columns.', + content: + '# Triage Coda Tracker Rows\n\nTriage incoming items (bugs, requests, tickets) stored in a Coda table.\n\n## Steps\n1. List rows filtered to the untriaged status value.\n2. For each row, decide priority and owner from its description.\n3. Update each row with the triage fields using column names or IDs.\n4. Push the row button column if the table uses a button to notify the owner.\n\n## Output\nA table of triaged rows with the priority and owner assigned to each.', + }, + { + name: 'audit-coda-doc-sharing', + description: + 'Review who a Coda doc is shared with and flag public links or access outside the company domain.', + content: + '# Audit Coda Doc Sharing\n\nCheck a Coda doc for overly broad access.\n\n## Steps\n1. List the permissions on the doc.\n2. Flag any permission shared with anyone with the link, or with an email or domain outside the company domain.\n3. Read the sharing settings to see whether editors can change permissions or viewers can copy the doc.\n4. Remove a permission only when the user confirms it should be revoked.\n\n## Output\nA list of risky permissions with the principal and access level, plus the current sharing settings.', + }, + { + name: 'export-coda-page-markdown', + description: + 'Export a Coda page as Markdown so its content can be summarized, archived, or synced elsewhere.', + content: + '# Export Coda Page as Markdown\n\nGet the full content of a Coda page as a Markdown file.\n\n## Steps\n1. Start a page export with Markdown as the output format.\n2. Check the export status with the returned export ID until the status is complete.\n3. Use the download link right away; it expires shortly after it is issued.\n4. If the export fails, read the page content directly as plain-text lines instead.\n\n## Output\nThe download link or the page text, plus the page name.', + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry-maps.ts b/apps/sim/blocks/registry-maps.ts index b75dc96d948..0f09cc1921e 100644 --- a/apps/sim/blocks/registry-maps.ts +++ b/apps/sim/blocks/registry-maps.ts @@ -44,6 +44,7 @@ import { CloudflareBlock, CloudflareBlockMeta } from '@/blocks/blocks/cloudflare import { CloudFormationBlock, CloudFormationBlockMeta } from '@/blocks/blocks/cloudformation' import { CloudTrailBlock, CloudTrailBlockMeta } from '@/blocks/blocks/cloudtrail' import { CloudWatchBlock, CloudWatchBlockMeta } from '@/blocks/blocks/cloudwatch' +import { CodaBlock, CodaBlockMeta } from '@/blocks/blocks/coda' import { CodePipelineBlock, CodePipelineBlockMeta } from '@/blocks/blocks/codepipeline' import { ConditionBlock } from '@/blocks/blocks/condition' import { ConfluenceBlock, ConfluenceBlockMeta, ConfluenceV2Block } from '@/blocks/blocks/confluence' @@ -426,6 +427,7 @@ export const BLOCK_REGISTRY: Record = { cloudformation: CloudFormationBlock, cloudtrail: CloudTrailBlock, cloudwatch: CloudWatchBlock, + coda: CodaBlock, codepipeline: CodePipelineBlock, condition: ConditionBlock, confluence: ConfluenceBlock, @@ -792,6 +794,7 @@ export const BLOCK_META_REGISTRY: Record = { cloudformation: CloudFormationBlockMeta, cloudtrail: CloudTrailBlockMeta, cloudwatch: CloudWatchBlockMeta, + coda: CodaBlockMeta, codepipeline: CodePipelineBlockMeta, confluence: ConfluenceBlockMeta, context_dev: ContextDevBlockMeta, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 867a81af5c2..165cd584113 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2326,6 +2326,17 @@ export function AtlassianIcon(props: SVGProps) { ) } +export function CodaIcon(props: SVGProps) { + return ( + + + + ) +} + export function ConfluenceIcon(props: SVGProps) { const id = useId() const topGradientId = `confluence_top_${id}` diff --git a/apps/sim/lib/block-metadata/names.generated.ts b/apps/sim/lib/block-metadata/names.generated.ts index 04f36d5f06b..6b50dd9f5f3 100644 --- a/apps/sim/lib/block-metadata/names.generated.ts +++ b/apps/sim/lib/block-metadata/names.generated.ts @@ -44,6 +44,7 @@ export const BLOCK_NAMES: Readonly> = { cloudformation: 'CloudFormation', cloudtrail: 'CloudTrail', cloudwatch: 'CloudWatch', + coda: 'Coda', codepipeline: 'CodePipeline', condition: 'Condition', confluence_v2: 'Confluence', diff --git a/apps/sim/lib/copilot/generated/docs-manifest.ts b/apps/sim/lib/copilot/generated/docs-manifest.ts index bced482121a..7a1c35fc2a2 100644 --- a/apps/sim/lib/copilot/generated/docs-manifest.ts +++ b/apps/sim/lib/copilot/generated/docs-manifest.ts @@ -109,6 +109,7 @@ export const DOCS_MANIFEST: readonly string[] = [ 'integrations/cloudformation.mdx', 'integrations/cloudtrail.mdx', 'integrations/cloudwatch.mdx', + 'integrations/coda.mdx', 'integrations/codepipeline.mdx', 'integrations/confluence.mdx', 'integrations/context_dev.mdx', diff --git a/apps/sim/lib/credentials/token-service-accounts/descriptors.ts b/apps/sim/lib/credentials/token-service-accounts/descriptors.ts index c2d089ddb3e..1ff8453881c 100644 --- a/apps/sim/lib/credentials/token-service-accounts/descriptors.ts +++ b/apps/sim/lib/credentials/token-service-accounts/descriptors.ts @@ -81,6 +81,7 @@ export const PIPEDRIVE_SERVICE_ACCOUNT_PROVIDER_ID = 'pipedrive-service-account' export const CLAUDE_PLATFORM_SERVICE_ACCOUNT_PROVIDER_ID = 'claude-platform-service-account' as const export const SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID = 'snowflake-service-account' as const +export const CODA_SERVICE_ACCOUNT_PROVIDER_ID = 'coda-service-account' as const const SHOPIFY_DOMAIN_HINT_REGEX = /^[a-z0-9][a-z0-9-]*\.myshopify\.com$/i @@ -109,6 +110,7 @@ export type TokenServiceAccountProviderId = | typeof PIPEDRIVE_SERVICE_ACCOUNT_PROVIDER_ID | typeof CLAUDE_PLATFORM_SERVICE_ACCOUNT_PROVIDER_ID | typeof SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID + | typeof CODA_SERVICE_ACCOUNT_PROVIDER_ID export const TOKEN_SERVICE_ACCOUNT_DESCRIPTORS: Record< TokenServiceAccountProviderId, @@ -407,6 +409,23 @@ export const TOKEN_SERVICE_ACCOUNT_DESCRIPTORS: Record< ], docsUrl: 'https://docs.sim.ai/integrations/managed-agent', }, + [CODA_SERVICE_ACCOUNT_PROVIDER_ID]: { + providerId: CODA_SERVICE_ACCOUNT_PROVIDER_ID, + serviceLabel: 'Coda', + tokenNoun: 'API token', + connectNoun: 'API token', + fields: [ + { + id: 'apiToken', + label: 'API token', + placeholder: 'Paste a Coda API token', + secret: true, + }, + ], + docsUrl: 'https://docs.sim.ai/integrations/coda', + helpText: + 'Create a token under Account settings → API settings. A token restricted to specific docs or tables can only read and write those.', + }, [SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID]: { providerId: SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID, serviceLabel: 'Snowflake', diff --git a/apps/sim/lib/credentials/token-service-accounts/server.ts b/apps/sim/lib/credentials/token-service-accounts/server.ts index b341006753a..4e6daa6582d 100644 --- a/apps/sim/lib/credentials/token-service-accounts/server.ts +++ b/apps/sim/lib/credentials/token-service-accounts/server.ts @@ -6,6 +6,7 @@ import { CALCOM_SERVICE_ACCOUNT_PROVIDER_ID, CLAUDE_PLATFORM_SERVICE_ACCOUNT_PROVIDER_ID, CLICKUP_SERVICE_ACCOUNT_PROVIDER_ID, + CODA_SERVICE_ACCOUNT_PROVIDER_ID, HARMONIC_SERVICE_ACCOUNT_PROVIDER_ID, HUBSPOT_SERVICE_ACCOUNT_PROVIDER_ID, isTokenServiceAccountProviderId, @@ -27,6 +28,7 @@ import { validateAttioServiceAccount } from '@/lib/credentials/token-service-acc import { validateCalcomServiceAccount } from '@/lib/credentials/token-service-accounts/validators/calcom' import { validateClaudePlatformServiceAccount } from '@/lib/credentials/token-service-accounts/validators/claude-platform' import { validateClickupServiceAccount } from '@/lib/credentials/token-service-accounts/validators/clickup' +import { validateCodaServiceAccount } from '@/lib/credentials/token-service-accounts/validators/coda' import { validateHarmonicServiceAccount } from '@/lib/credentials/token-service-accounts/validators/harmonic' import { validateHubspotServiceAccount } from '@/lib/credentials/token-service-accounts/validators/hubspot' import { validateLinearServiceAccount } from '@/lib/credentials/token-service-accounts/validators/linear' @@ -100,6 +102,7 @@ const TOKEN_SERVICE_ACCOUNT_VALIDATORS: Record< [PIPEDRIVE_SERVICE_ACCOUNT_PROVIDER_ID]: validatePipedriveServiceAccount, [CLAUDE_PLATFORM_SERVICE_ACCOUNT_PROVIDER_ID]: validateClaudePlatformServiceAccount, [SNOWFLAKE_SERVICE_ACCOUNT_PROVIDER_ID]: validateSnowflakeServiceAccount, + [CODA_SERVICE_ACCOUNT_PROVIDER_ID]: validateCodaServiceAccount, } export function getTokenServiceAccountValidator( diff --git a/apps/sim/lib/credentials/token-service-accounts/validators/coda.test.ts b/apps/sim/lib/credentials/token-service-accounts/validators/coda.test.ts new file mode 100644 index 00000000000..4bb418a3510 --- /dev/null +++ b/apps/sim/lib/credentials/token-service-accounts/validators/coda.test.ts @@ -0,0 +1,97 @@ +/** + * @vitest-environment node + */ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { + CODA_SERVICE_ACCOUNT_PROVIDER_ID, + TOKEN_SERVICE_ACCOUNT_DESCRIPTORS, +} from '@/lib/credentials/token-service-accounts/descriptors' +import { TokenServiceAccountValidationError } from '@/lib/credentials/token-service-accounts/errors' +import { getTokenServiceAccountValidator } from '@/lib/credentials/token-service-accounts/server' +import { validateCodaServiceAccount } from '@/lib/credentials/token-service-accounts/validators/coda' + +const mockFetch = vi.fn() + +function jsonResponse(status: number, body: unknown): Response { + return new Response(JSON.stringify(body), { + status, + headers: { 'Content-Type': 'application/json' }, + }) +} + +describe('validateCodaServiceAccount', () => { + beforeEach(() => { + vi.stubGlobal('fetch', mockFetch) + mockFetch.mockReset() + }) + + afterEach(() => { + vi.unstubAllGlobals() + }) + + it('is registered with its descriptor', () => { + expect(getTokenServiceAccountValidator(CODA_SERVICE_ACCOUNT_PROVIDER_ID)).toBe( + validateCodaServiceAccount + ) + expect(TOKEN_SERVICE_ACCOUNT_DESCRIPTORS[CODA_SERVICE_ACCOUNT_PROVIDER_ID]).toMatchObject({ + serviceLabel: 'Coda', + fields: [{ id: 'apiToken', secret: true }], + }) + }) + + it('returns the token owner as principal and workspace metadata', async () => { + mockFetch.mockResolvedValue( + jsonResponse(200, { + name: 'Jane Doe', + loginId: 'jane@example.com', + type: 'user', + scoped: false, + tokenName: 'Sim workflows', + href: 'https://coda.io/apis/v1/whoami', + workspace: { id: 'ws-1Ab234', type: 'workspace', name: 'Acme' }, + }) + ) + + const result = await validateCodaServiceAccount({ apiToken: 'coda-token' }) + + expect(result).toEqual({ + displayName: 'Sim workflows (jane@example.com)', + principal: { kind: 'user', id: 'jane@example.com', label: 'Jane Doe' }, + auditMetadata: { codaWorkspaceId: 'ws-1Ab234' }, + storedMetadata: { workspaceId: 'ws-1Ab234', scoped: 'false', tokenName: 'Sim workflows' }, + }) + expect(mockFetch).toHaveBeenCalledWith('https://coda.io/apis/v1/whoami', { + headers: { Authorization: 'Bearer coda-token', Accept: 'application/json' }, + redirect: 'error', + signal: expect.any(AbortSignal), + }) + }) + + it('maps 401 to invalid_credentials', async () => { + mockFetch.mockResolvedValue( + jsonResponse(401, { statusCode: 401, statusMessage: 'Unauthorized', message: 'Unauthorized' }) + ) + + const error = await validateCodaServiceAccount({ apiToken: 'bad' }).catch((e) => e) + + expect(error).toBeInstanceOf(TokenServiceAccountValidationError) + expect(error.code).toBe('invalid_credentials') + expect(error.status).toBe(401) + }) + + it('maps 429 and 500 to provider_unavailable', async () => { + for (const status of [429, 500]) { + mockFetch.mockResolvedValueOnce(jsonResponse(status, { message: 'nope' })) + const error = await validateCodaServiceAccount({ apiToken: 'coda-token' }).catch((e) => e) + expect(error.code).toBe('provider_unavailable') + } + }) + + it('rejects a success body without a login id', async () => { + for (const body of [{ name: 'Jane' }, null, { loginId: ' ' }]) { + mockFetch.mockResolvedValueOnce(jsonResponse(200, body)) + const error = await validateCodaServiceAccount({ apiToken: 'coda-token' }).catch((e) => e) + expect(error.code).toBe('provider_unavailable') + } + }) +}) diff --git a/apps/sim/lib/credentials/token-service-accounts/validators/coda.ts b/apps/sim/lib/credentials/token-service-accounts/validators/coda.ts new file mode 100644 index 00000000000..dddb0909b68 --- /dev/null +++ b/apps/sim/lib/credentials/token-service-accounts/validators/coda.ts @@ -0,0 +1,64 @@ +import { userPrincipal } from '@/lib/credentials/principal' +import { + fetchProvider, + parseProviderJson, + TokenServiceAccountValidationError, + throwForProviderResponse, +} from '@/lib/credentials/token-service-accounts/errors' +import type { + TokenServiceAccountFields, + TokenServiceAccountValidationResult, +} from '@/lib/credentials/token-service-accounts/server' +import { CODA_API_BASE, codaHeaders } from '@/tools/coda/utils' + +const CODA_WHOAMI_URL = `${CODA_API_BASE}/whoami` + +interface CodaWhoamiResponse { + name?: string + loginId?: string + tokenName?: string + scoped?: boolean + workspace?: { id?: string; name?: string } +} + +/** + * Validates a Coda API token by calling `GET /whoami`, which every token may + * call regardless of doc or table restrictions. The header set comes from the + * same helper the runtime tools use, so a token that verifies here is proven + * against the exact request shape tools send. Coda exposes no numeric user id, + * so the login email is the principal id. + */ +export async function validateCodaServiceAccount( + fields: TokenServiceAccountFields +): Promise { + const res = await fetchProvider( + CODA_WHOAMI_URL, + { headers: codaHeaders(fields.apiToken), redirect: 'error' }, + 'whoami' + ) + await throwForProviderResponse(res, 'whoami') + + const body = await parseProviderJson(res, 'whoami') + if (typeof body?.loginId !== 'string' || !body.loginId.trim()) { + throw new TokenServiceAccountValidationError('provider_unavailable', 502, { + step: 'whoami', + reason: 'missing loginId in response', + }) + } + + const auditMetadata: Record = {} + const storedMetadata: Record = {} + if (body.workspace?.id) { + auditMetadata.codaWorkspaceId = body.workspace.id + storedMetadata.workspaceId = body.workspace.id + } + if (typeof body.scoped === 'boolean') storedMetadata.scoped = String(body.scoped) + if (body.tokenName) storedMetadata.tokenName = body.tokenName + + return { + displayName: body.tokenName ? `${body.tokenName} (${body.loginId})` : body.loginId, + principal: userPrincipal(body.loginId, body.name), + auditMetadata, + storedMetadata, + } +} diff --git a/apps/sim/lib/integrations/credential-display.test.ts b/apps/sim/lib/integrations/credential-display.test.ts index cc138c9d705..69dbcfff521 100644 --- a/apps/sim/lib/integrations/credential-display.test.ts +++ b/apps/sim/lib/integrations/credential-display.test.ts @@ -49,6 +49,7 @@ const EXPECTED_COVERAGE: Record = { 'calcom-service-account': ['cal-com'], 'claude-platform-service-account': [], 'clickup-service-account': ['clickup'], + 'coda-service-account': [], 'github-app-installation': ['github'], 'google-service-account': [ 'gmail', diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index bad2b73663e..6167ee0d684 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -46,6 +46,7 @@ import { CloudflareIcon, CloudTrailIcon, CloudWatchIcon, + CodaIcon, CodePipelineIcon, ConfluenceIcon, ContextDevIcon, @@ -319,6 +320,7 @@ export const blockTypeToIconMap: Record = { cloudformation: CloudFormationIcon, cloudtrail: CloudTrailIcon, cloudwatch: CloudWatchIcon, + coda: CodaIcon, codepipeline: CodePipelineIcon, confluence: ConfluenceIcon, confluence_v2: ConfluenceIcon, diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 1d6891b9d6c..8ab9ddcd9ba 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -11,6 +11,7 @@ import { CalComIcon, ClaudeIcon, ClickUpIcon, + CodaIcon, ConfluenceIcon, DocuSignIcon, DropboxIcon, @@ -1319,6 +1320,23 @@ export const OAUTH_PROVIDERS: Record = { }, defaultService: 'hubspot', }, + coda: { + name: 'Coda', + icon: CodaIcon, + services: { + coda: { + name: 'Coda', + description: 'Read and write Coda docs, pages, and tables.', + providerId: 'coda', + serviceAccountProviderId: 'coda-service-account', + icon: CodaIcon, + baseProviderIcon: CodaIcon, + scopes: [], + authType: 'service_account', + }, + }, + defaultService: 'coda', + }, harmonic: { name: 'Harmonic', icon: HarmonicIcon, diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index b97bc4fd9e8..393fcf13d7f 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -83,6 +83,7 @@ export type OAuthProvider = | 'quickbooks' | 'hubspot' | 'harmonic' + | 'coda' | 'salesforce' | 'linkedin' | 'instagram' @@ -145,6 +146,7 @@ export type OAuthService = | 'quickbooks' | 'hubspot' | 'harmonic' + | 'coda' | 'salesforce' | 'linkedin' | 'instagram' diff --git a/apps/sim/lib/selectors/manifest.test.ts b/apps/sim/lib/selectors/manifest.test.ts index f8a41558c4b..e51805d591c 100644 --- a/apps/sim/lib/selectors/manifest.test.ts +++ b/apps/sim/lib/selectors/manifest.test.ts @@ -9,8 +9,8 @@ describe('selector manifest', () => { const count = (classification: (typeof classifications)[number]) => classifications.filter((value) => value === classification).length - expect(Object.keys(selectorManifest)).toHaveLength(98) - expect(count('provider-server')).toBe(85) + expect(Object.keys(selectorManifest)).toHaveLength(107) + expect(count('provider-server')).toBe(94) expect(count('internal-server')).toBe(12) expect(count('local')).toBe(1) expect(classifications).not.toContain('provider-legacy') @@ -36,7 +36,7 @@ describe('selector manifest', () => { const rawConnectionKeys = providerKeys.filter( (key) => !serverSelectorRegistry[key as keyof typeof serverSelectorRegistry].credential ) - expect(providerKeys).toHaveLength(85) + expect(providerKeys).toHaveLength(94) expect(rawConnectionKeys.sort()).toEqual([ 'cloudwatch.logGroups', 'cloudwatch.logStreams', diff --git a/apps/sim/lib/selectors/manifest.ts b/apps/sim/lib/selectors/manifest.ts index 6d65ad50ea5..7369e6f956a 100644 --- a/apps/sim/lib/selectors/manifest.ts +++ b/apps/sim/lib/selectors/manifest.ts @@ -125,6 +125,49 @@ export const selectorManifest = { any: ['folderId', 'spaceId', 'listSpaceId'], }, }), + 'coda.docs': providerSelector([], { + listMode: 'paginated', + search: true, + detail: true, + unknownDetail: true, + staleTime: SEARCH_SELECTOR_STALE_TIME, + }), + 'coda.pages': providerSelector(['docId'], { + readiness: { all: ['oauthCredential', 'docId'] }, + detail: true, + unknownDetail: true, + }), + 'coda.tables': providerSelector(['docId'], { + readiness: { all: ['oauthCredential', 'docId'] }, + detail: true, + unknownDetail: true, + }), + 'coda.columns': providerSelector(['docId', 'tableId'], { + readiness: { all: ['oauthCredential', 'docId', 'tableId'] }, + detail: true, + unknownDetail: true, + }), + 'coda.rows': providerSelector(['docId', 'tableId'], { + readiness: { all: ['oauthCredential', 'docId', 'tableId'] }, + listMode: 'paginated', + detail: true, + unknownDetail: true, + }), + 'coda.formulas': providerSelector(['docId'], { + readiness: { all: ['oauthCredential', 'docId'] }, + detail: true, + unknownDetail: true, + }), + 'coda.controls': providerSelector(['docId'], { + readiness: { all: ['oauthCredential', 'docId'] }, + detail: true, + unknownDetail: true, + }), + 'coda.folders': providerSelector([], { detail: true, unknownDetail: true }), + 'coda.permissions': providerSelector(['docId'], { + readiness: { all: ['oauthCredential', 'docId'] }, + detail: true, + }), 'confluence.spaces': providerSelector(['domain'], { readiness: { all: ['oauthCredential', 'domain'] }, listMode: 'paginated', diff --git a/apps/sim/lib/selectors/server/providers/coda.test.ts b/apps/sim/lib/selectors/server/providers/coda.test.ts new file mode 100644 index 00000000000..3aec6555415 --- /dev/null +++ b/apps/sim/lib/selectors/server/providers/coda.test.ts @@ -0,0 +1,179 @@ +/** + * @vitest-environment node + */ +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockFetch, mockResolveCredentialBundle } = vi.hoisted(() => ({ + mockFetch: vi.fn(), + mockResolveCredentialBundle: vi.fn(), +})) + +vi.mock('@/lib/selectors/server/providers/credential-bundle', () => ({ + resolveSelectorCredentialBundle: mockResolveCredentialBundle, +})) + +import type { ServerSelectorKey } from '@/lib/selectors/manifest' +import { SelectorContextUnavailableError } from '@/lib/selectors/server/errors' +import { createSelectorProtectedValues } from '@/lib/selectors/server/protected-values' +import { codaSelectorAttachments } from '@/lib/selectors/server/providers/coda' +import type { ExecuteServerSelectorArgs } from '@/lib/selectors/server/types' +import type { SelectorContext, SelectorRequest } from '@/lib/selectors/types' + +function args( + selectorKey: ServerSelectorKey, + request: SelectorRequest, + context: SelectorContext = {} +): ExecuteServerSelectorArgs { + return { + selectorKey, + context: { oauthCredential: 'credential-1', ...context }, + request, + scope: { kind: 'workspace', workspaceId: 'workspace-1' }, + workspaceId: 'workspace-1', + principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' }, + requesterUserId: 'user-1', + credential: { suppliedId: 'credential-1' }, + references: new Map(), + protectedValues: createSelectorProtectedValues(), + } +} + +function json(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { status }) +} + +describe('Coda server selector adapter', () => { + beforeEach(() => { + vi.clearAllMocks() + vi.stubGlobal('fetch', mockFetch) + mockResolveCredentialBundle.mockResolvedValue({ accessToken: 'server-only-token' }) + }) + + afterAll(() => vi.unstubAllGlobals()) + + it('continues a doc search with only the Coda page token and returns the next cursor', async () => { + mockFetch.mockResolvedValueOnce( + json({ items: [{ id: 'doc1', name: 'Roadmap', owner: 'a@b.co' }], nextPageToken: 'tok2' }) + ) + + const result = await codaSelectorAttachments['coda.docs'].execute( + args('coda.docs', { kind: 'list', search: ' road ', cursor: 'tok1' }) + ) + + expect(result).toEqual({ + kind: 'list', + items: [{ id: 'doc1', label: 'Roadmap' }], + nextCursor: 'tok2', + }) + const [url, init] = mockFetch.mock.calls[0] + expect(mockFetch).toHaveBeenCalledTimes(1) + expect(url).toBe('https://coda.io/apis/v1/docs?pageToken=tok1') + expect(init.headers).toEqual({ + Authorization: 'Bearer server-only-token', + Accept: 'application/json', + }) + }) + + it('sends the search term and page size on the first doc page', async () => { + mockFetch.mockResolvedValueOnce(json({ items: [] })) + + await codaSelectorAttachments['coda.docs'].execute( + args('coda.docs', { kind: 'list', search: ' road ' }) + ) + + expect(mockFetch.mock.calls[0][0]).toBe('https://coda.io/apis/v1/docs?limit=100&query=road') + }) + + it('reads every page of a doc-scoped list into one flat result', async () => { + mockFetch + .mockResolvedValueOnce( + json({ items: [{ id: 'grid-1', name: 'Tasks', tableType: 'table' }], nextPageToken: 'p2' }) + ) + .mockResolvedValueOnce(json({ items: [{ id: 'table-2', name: 'Open', tableType: 'view' }] })) + + const result = await codaSelectorAttachments['coda.tables'].execute( + args('coda.tables', { kind: 'list' }, { docId: 'AbCDeFGH' }) + ) + + expect(result).toEqual({ + kind: 'list', + items: [ + { id: 'grid-1', label: 'Tasks', meta: { tableType: 'table' } }, + { id: 'table-2', label: 'Open (view)', meta: { tableType: 'view' } }, + ], + }) + expect(mockFetch.mock.calls[1][0]).toBe( + 'https://coda.io/apis/v1/docs/AbCDeFGH/tables?pageToken=p2' + ) + }) + + it('scopes columns and rows to the selected doc and table', async () => { + mockFetch.mockResolvedValueOnce( + json({ items: [{ id: 'c-1', name: 'Status', format: { type: 'select', isArray: false } }] }) + ) + + await expect( + codaSelectorAttachments['coda.columns'].execute( + args('coda.columns', { kind: 'list' }, { docId: 'doc', tableId: 'grid 1' }) + ) + ).resolves.toEqual({ + kind: 'list', + items: [{ id: 'c-1', label: 'Status', meta: { formatType: 'select' } }], + }) + expect(mockFetch.mock.calls[0][0]).toBe( + 'https://coda.io/apis/v1/docs/doc/tables/grid%201/columns?limit=100' + ) + }) + + it('resolves a missing resource detail to no option', async () => { + mockFetch.mockResolvedValueOnce(json({ message: 'Not Found' }, 404)) + + await expect( + codaSelectorAttachments['coda.pages'].execute( + args('coda.pages', { kind: 'detail', id: 'canvas-gone' }, { docId: 'doc' }) + ) + ).resolves.toEqual({ kind: 'detail', item: null }) + expect(mockFetch.mock.calls[0][0]).toBe('https://coda.io/apis/v1/docs/doc/pages/canvas-gone') + }) + + it('labels permissions by principal and resolves details from the list', async () => { + mockFetch.mockResolvedValue( + json({ + items: [ + { id: 'perm-1', access: 'write', principal: { type: 'email', email: 'a@b.co' } }, + { id: 'perm-2', access: 'readonly', principal: { type: 'anyone' } }, + ], + }) + ) + + await expect( + codaSelectorAttachments['coda.permissions'].execute( + args('coda.permissions', { kind: 'detail', id: 'perm-2' }, { docId: 'doc' }) + ) + ).resolves.toEqual({ + kind: 'detail', + item: { + id: 'perm-2', + label: 'Anyone with the link (readonly)', + meta: { access: 'readonly', principalType: 'anyone' }, + }, + }) + }) + + it('rejects missing or traversal context before contacting Coda', async () => { + await expect( + codaSelectorAttachments['coda.pages'].execute(args('coda.pages', { kind: 'list' })) + ).rejects.toBeInstanceOf(SelectorContextUnavailableError) + await expect( + codaSelectorAttachments['coda.rows'].execute( + args('coda.rows', { kind: 'list' }, { docId: 'doc', tableId: '..' }) + ) + ).rejects.toBeInstanceOf(SelectorContextUnavailableError) + await expect( + codaSelectorAttachments['coda.docs'].execute( + args('coda.docs', { kind: 'list', cursor: 'bad token' }) + ) + ).rejects.toBeInstanceOf(SelectorContextUnavailableError) + expect(mockFetch).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/lib/selectors/server/providers/coda.ts b/apps/sim/lib/selectors/server/providers/coda.ts new file mode 100644 index 00000000000..14db8f8e837 --- /dev/null +++ b/apps/sim/lib/selectors/server/providers/coda.ts @@ -0,0 +1,369 @@ +import { truncate } from '@sim/utils/string' +import { z } from 'zod' +import { MAX_SELECTOR_OPTIONS } from '@/lib/selectors/limits' +import type { ServerSelectorKey } from '@/lib/selectors/manifest' +import { + SelectorContextUnavailableError, + SelectorOptionsUnavailableError, +} from '@/lib/selectors/server/errors' +import { appendSelectorOptions } from '@/lib/selectors/server/option-budget' +import { resolveSelectorCredentialBundle } from '@/lib/selectors/server/providers/credential-bundle' +import { + fetchProviderJson, + fetchProviderJsonWithStatus, +} from '@/lib/selectors/server/providers/provider-http' +import { + detailSelectorResult, + type ExecuteServerSelectorArgs, + listSelectorResult, + type ServerSelectorAttachment, + type ServerSelectorAttachmentMap, + type ServerSelectorExecutionResult, +} from '@/lib/selectors/server/types' +import type { SafeSelectorOption } from '@/lib/selectors/types' +import { buildCodaUrl, codaHeaders } from '@/tools/coda/utils' +import { safeUrlPathSegment } from '@/tools/url-path' + +type CodaSelectorKey = Extract + +const CODA_PAGE_SIZE = 100 +const CODA_MAX_FLAT_PAGES = 20 +const CODA_PAGE_TOKEN_PATTERN = /^[\x21-\x7e]{1,4096}$/ + +const namedItemSchema = z.object({ + id: z.string().min(1).max(512), + name: z.string().optional(), +}) + +const tableItemSchema = namedItemSchema.extend({ tableType: z.string().max(64).optional() }) + +const columnItemSchema = namedItemSchema.extend({ + format: z + .object({ type: z.string().max(64).optional() }) + .passthrough() + .optional(), +}) + +const permissionItemSchema = z.object({ + id: z.string().min(1).max(512), + access: z.string().max(64), + principal: z + .object({ + type: z.string().max(64).optional(), + email: z.string().max(1_024).optional(), + groupName: z.string().max(1_024).optional(), + domain: z.string().max(1_024).optional(), + workspaceId: z.string().max(512).optional(), + }) + .optional(), +}) + +function pageSchema(item: T) { + return z.object({ + items: z.array(item).max(1_000).optional(), + nextPageToken: z.string().max(4_096).optional(), + }) +} + +type NamedItem = z.infer +type TableItem = z.infer +type ColumnItem = z.infer +type PermissionItem = z.infer + +async function codaAccessToken(args: ExecuteServerSelectorArgs): Promise { + const { accessToken } = await resolveSelectorCredentialBundle({ + credential: args.credential, + protectedValues: args.protectedValues, + }) + return accessToken +} + +/** Encodes a context value or requested id as one path segment, rejecting traversal input. */ +function segment(value: string | undefined, paramName: string): string { + const trimmed = value?.trim() + if (!trimmed) throw new SelectorContextUnavailableError() + try { + return safeUrlPathSegment(trimmed, paramName) + } catch { + throw new SelectorContextUnavailableError() + } +} + +function docPath(args: ExecuteServerSelectorArgs): string { + return `/docs/${segment(args.context.docId, 'docId')}` +} + +function tablePath(args: ExecuteServerSelectorArgs): string { + return `${docPath(args)}/tables/${segment(args.context.tableId, 'tableId')}` +} + +function requireCursor(cursor: string | undefined): string | undefined { + if (cursor === undefined) return undefined + if (!CODA_PAGE_TOKEN_PATTERN.test(cursor)) throw new SelectorContextUnavailableError() + return cursor +} + +async function fetchPage( + args: ExecuteServerSelectorArgs, + accessToken: string, + path: string, + schema: T, + query: Record +): Promise>>> { + const body = await fetchProviderJson(buildCodaUrl(path, query), { + headers: codaHeaders(accessToken), + signal: args.signal, + }) + const parsed = pageSchema(schema).safeParse(body) + if (!parsed.success) throw new SelectorOptionsUnavailableError() + return parsed.data +} + +/** Reads every page of a bounded Coda list into one flat option set. */ +async function listAllPages( + args: ExecuteServerSelectorArgs, + path: string, + schema: T, + toOption: (item: z.infer) => SafeSelectorOption +): Promise { + const accessToken = await codaAccessToken(args) + const options: SafeSelectorOption[] = [] + let pageToken: string | undefined + let truncated = false + + for (let page = 0; page < CODA_MAX_FLAT_PAGES; page++) { + const data = await fetchPage(args, accessToken, path, schema, { + limit: CODA_PAGE_SIZE, + pageToken, + }) + const appended = appendSelectorOptions(options, (data.items ?? []).map(toOption)) + pageToken = data.nextPageToken + if (!pageToken) { + if (appended.overflow) truncated = true + break + } + if (appended.full || page === CODA_MAX_FLAT_PAGES - 1) { + truncated = true + break + } + } + + return listSelectorResult( + options, + undefined, + truncated + ? { + truncated: { + reason: 'provider-cap', + limit: MAX_SELECTOR_OPTIONS, + pages: CODA_MAX_FLAT_PAGES, + }, + } + : undefined + ) +} + +/** Resolves one resource by id; a missing or deleted resource resolves to no option. */ +async function getDetail( + args: ExecuteServerSelectorArgs, + path: string, + schema: T, + toOption: (item: z.infer) => SafeSelectorOption +): Promise { + const accessToken = await codaAccessToken(args) + const result = await fetchProviderJsonWithStatus( + buildCodaUrl(path), + { headers: codaHeaders(accessToken), signal: args.signal }, + { passthroughStatuses: [404, 410] } + ) + if (!result.ok) return detailSelectorResult(null) + const parsed = schema.safeParse(result.data) + if (!parsed.success) throw new SelectorOptionsUnavailableError() + return detailSelectorResult(toOption(parsed.data)) +} + +function namedOption(item: NamedItem): SafeSelectorOption { + return { id: item.id, label: truncate(item.name?.trim() || item.id, 200) } +} + +function tableOption(item: TableItem): SafeSelectorOption { + const name = truncate(item.name?.trim() || item.id, 200) + return { + id: item.id, + label: item.tableType === 'view' ? `${name} (view)` : name, + ...(item.tableType ? { meta: { tableType: item.tableType } } : {}), + } +} + +function columnOption(item: ColumnItem): SafeSelectorOption { + const formatType = item.format?.type + return { + id: item.id, + label: truncate(item.name?.trim() || item.id, 200), + ...(formatType ? { meta: { formatType } } : {}), + } +} + +function permissionOption(item: PermissionItem): SafeSelectorOption { + const principal = item.principal + const who = + principal?.type === 'anyone' + ? 'Anyone with the link' + : principal?.email || + principal?.groupName || + principal?.domain || + principal?.workspaceId || + item.id + return { + id: item.id, + label: `${who} (${item.access})`, + meta: { access: item.access, ...(principal?.type ? { principalType: principal.type } : {}) }, + } +} + +/** + * Docs and rows can number in the thousands, so they page through the selector + * cursor instead of being read eagerly. Docs support Coda's server-side search. + */ +async function executeDocs(args: ExecuteServerSelectorArgs) { + if (args.request.kind === 'detail') { + return getDetail( + args, + `/docs/${segment(args.request.id, 'docId')}`, + namedItemSchema, + namedOption + ) + } + const accessToken = await codaAccessToken(args) + const data = await fetchPage(args, accessToken, '/docs', namedItemSchema, { + limit: CODA_PAGE_SIZE, + query: args.request.search?.trim() || undefined, + pageToken: requireCursor(args.request.cursor), + }) + return listSelectorResult((data.items ?? []).map(namedOption), data.nextPageToken) +} + +async function executeRows(args: ExecuteServerSelectorArgs) { + if (args.request.kind === 'detail') { + return getDetail( + args, + `${tablePath(args)}/rows/${segment(args.request.id, 'rowId')}`, + namedItemSchema, + namedOption + ) + } + const accessToken = await codaAccessToken(args) + const data = await fetchPage(args, accessToken, `${tablePath(args)}/rows`, namedItemSchema, { + limit: CODA_PAGE_SIZE, + pageToken: requireCursor(args.request.cursor), + }) + return listSelectorResult((data.items ?? []).map(namedOption), data.nextPageToken) +} + +function docScopedAttachment(input: { + collection: string + idParam: string + schema: T + toOption: (item: z.infer) => SafeSelectorOption + scope?: (args: ExecuteServerSelectorArgs) => string +}): ServerSelectorAttachment { + const scope = input.scope ?? docPath + return { + credential, + integrationBlockTypes, + destination: 'fixed', + execute: async (args) => + args.request.kind === 'detail' + ? getDetail( + args, + `${scope(args)}/${input.collection}/${segment(args.request.id, input.idParam)}`, + input.schema, + input.toOption + ) + : listAllPages(args, `${scope(args)}/${input.collection}`, input.schema, input.toOption), + } +} + +async function executePermissions(args: ExecuteServerSelectorArgs) { + const listed = await listAllPages( + args, + `${docPath(args)}/acl/permissions`, + permissionItemSchema, + permissionOption + ) + if (args.request.kind === 'list' || listed.kind !== 'list') return listed + const id = args.request.id + return { + ...detailSelectorResult(listed.items.find((item) => item.id === id) ?? null), + ...(listed.diagnostics ? { diagnostics: listed.diagnostics } : {}), + } +} + +const credential = { + kind: 'stored', + field: 'oauthCredential', + serviceIds: ['coda'], +} as const + +/** + * The integration this selector reaches. Declared rather than derived: Coda is an + * API-key integration with no entry in the deployment OAuth catalog, so its + * service id maps to no block type. + */ +const integrationBlockTypes = ['coda'] as const + +export const codaSelectorAttachments = { + 'coda.docs': { credential, integrationBlockTypes, destination: 'fixed', execute: executeDocs }, + 'coda.pages': docScopedAttachment({ + collection: 'pages', + idParam: 'pageId', + schema: namedItemSchema, + toOption: namedOption, + }), + 'coda.tables': docScopedAttachment({ + collection: 'tables', + idParam: 'tableId', + schema: tableItemSchema, + toOption: tableOption, + }), + 'coda.columns': docScopedAttachment({ + collection: 'columns', + idParam: 'columnId', + schema: columnItemSchema, + toOption: columnOption, + scope: tablePath, + }), + 'coda.rows': { credential, integrationBlockTypes, destination: 'fixed', execute: executeRows }, + 'coda.formulas': docScopedAttachment({ + collection: 'formulas', + idParam: 'formulaId', + schema: namedItemSchema, + toOption: namedOption, + }), + 'coda.controls': docScopedAttachment({ + collection: 'controls', + idParam: 'controlId', + schema: namedItemSchema, + toOption: namedOption, + }), + 'coda.folders': { + credential, + integrationBlockTypes, + destination: 'fixed', + execute: async (args) => + args.request.kind === 'detail' + ? getDetail( + args, + `/folders/${segment(args.request.id, 'folderId')}`, + namedItemSchema, + namedOption + ) + : listAllPages(args, '/folders', namedItemSchema, namedOption), + }, + 'coda.permissions': { + credential, + integrationBlockTypes, + destination: 'fixed', + execute: executePermissions, + }, +} satisfies ServerSelectorAttachmentMap diff --git a/apps/sim/lib/selectors/server/registry.ts b/apps/sim/lib/selectors/server/registry.ts index 09441960e09..d1574ae2d19 100644 --- a/apps/sim/lib/selectors/server/registry.ts +++ b/apps/sim/lib/selectors/server/registry.ts @@ -8,6 +8,7 @@ import { bitbucketSelectorAttachments } from '@/lib/selectors/server/providers/b import { calcomSelectorAttachments } from '@/lib/selectors/server/providers/calcom' import { clickupSelectorAttachments } from '@/lib/selectors/server/providers/clickup' import { cloudWatchSelectorAttachments } from '@/lib/selectors/server/providers/cloudwatch' +import { codaSelectorAttachments } from '@/lib/selectors/server/providers/coda' import { confluenceSelectorAttachments } from '@/lib/selectors/server/providers/confluence' import { githubSelectorAttachments } from '@/lib/selectors/server/providers/github' import { googleSelectorAttachments } from '@/lib/selectors/server/providers/google' @@ -45,6 +46,7 @@ export const serverSelectorRegistry = { ...calcomSelectorAttachments, ...clickupSelectorAttachments, ...cloudWatchSelectorAttachments, + ...codaSelectorAttachments, ...confluenceSelectorAttachments, ...googleSelectorAttachments, ...githubSelectorAttachments, diff --git a/apps/sim/lib/selectors/types.ts b/apps/sim/lib/selectors/types.ts index 2a20eac3cde..6d665f17c6f 100644 --- a/apps/sim/lib/selectors/types.ts +++ b/apps/sim/lib/selectors/types.ts @@ -16,6 +16,7 @@ export const selectorContextKeys = [ 'driveId', 'excludeWorkflowId', 'baseId', + 'docId', 'datasetId', 'serviceDeskId', 'impersonateUserEmail', diff --git a/apps/sim/scripts/check-block-registry.ts b/apps/sim/scripts/check-block-registry.ts index 6264d8ebaad..694f9d4928e 100644 --- a/apps/sim/scripts/check-block-registry.ts +++ b/apps/sim/scripts/check-block-registry.ts @@ -37,17 +37,21 @@ const gitOpts = { encoding: 'utf-8' as const, cwd: gitRoot } type IdMap = Record> /** - * Extracts subblock IDs from the `subBlocks: [ ... ]` section of a block - * definition. Only grabs the top-level `id:` of each subblock object — - * ignores nested IDs inside `options`, `columns`, etc. + * Returns the index of the `[` opening the first `subBlocks:` array literal in + * `source`, or null when that `subBlocks` value is an expression instead. */ -function extractSubBlockIds(source: string): string[] { - const startIdx = source.indexOf('subBlocks:') - if (startIdx === -1) return [] - - const bracketStart = source.indexOf('[', startIdx) - if (bracketStart === -1) return [] +function findSubBlocksLiteral(source: string): number | null { + const match = /subBlocks:\s*(\S)/.exec(source) + if (!match || match[1] !== '[') return null + return match.index + match[0].length - 1 +} +/** + * Extracts subblock IDs from the `subBlocks: [ ... ]` array literal whose + * opening bracket is at `bracketStart`. Only grabs the top-level `id:` of each + * subblock object — ignores nested IDs inside `options`, `columns`, etc. + */ +function extractSubBlockIds(source: string, bracketStart: number): string[] { const ids: string[] = [] let braceDepth = 0 let bracketDepth = 0 @@ -93,22 +97,40 @@ type PreviousIdsResult = | { kind: 'noop' } | { kind: 'ok'; map: IdMap } +/** + * Reads a block's subblock IDs from its source at the base ref. A file can + * declare an untyped legacy block before the typed block, so a typed block + * with a `subBlocks` array literal is read from its own definition. A typed + * block that derives `subBlocks` (for example by filtering the legacy block's) + * cannot be evaluated here, so it is read from the legacy literal: IDs the + * derivation already dropped then look removed. That fails closed while the + * file is being edited, and the block is skipped while the file is unchanged, + * since this diff cannot have removed anything from it. + */ +function extractPreviousIds(content: string, definitionStart: number, fileChanged: boolean) { + const ownLiteral = findSubBlocksLiteral(content.slice(definitionStart)) + if (ownLiteral !== null) return extractSubBlockIds(content, definitionStart + ownLiteral) + if (!fileChanged) return [] + const legacyLiteral = findSubBlocksLiteral(content) + return legacyLiteral === null ? [] : extractSubBlockIds(content, legacyLiteral) +} + function getPreviousIds(): PreviousIdsResult { const registryPath = 'apps/sim/blocks/registry.ts' const blocksDir = 'apps/sim/blocks/blocks' - let hasChanges = false + let changedPaths: Set try { const diff = execSync( `git diff --name-only ${baseRef} -- ${registryPath} ${blocksDir}`, gitOpts ).trim() - hasChanges = diff.length > 0 + changedPaths = new Set(diff ? diff.split('\n') : []) } catch { return { kind: 'skip', reason: 'Could not diff against base ref' } } - if (!hasChanges) { + if (changedPaths.size === 0) { return { kind: 'noop' } } @@ -134,7 +156,7 @@ function getPreviousIds(): PreviousIdsResult { if (!typeMatch) continue const blockType = typeMatch[1] - const ids = extractSubBlockIds(content) + const ids = extractPreviousIds(content, typeMatch.index ?? 0, changedPaths.has(filePath)) if (ids.length === 0) continue map[blockType] = new Set(ids) diff --git a/apps/sim/tools/coda/add_custom_domain.ts b/apps/sim/tools/coda/add_custom_domain.ts new file mode 100644 index 00000000000..b0bea6607f2 --- /dev/null +++ b/apps/sim/tools/coda/add_custom_domain.ts @@ -0,0 +1,47 @@ +import type { CodaCustomDomainParams, CodaCustomDomainResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + CUSTOM_DOMAIN_PARAM, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaAddCustomDomainTool: ToolConfig = + { + id: 'coda_add_custom_domain', + name: 'Coda Add Custom Domain', + description: + 'Connect a custom domain to a published Coda doc. Requires a Coda plan with custom domains.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM, customDocDomain: CUSTOM_DOMAIN_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'domains')), + method: 'POST', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => ({ customDocDomain: String(params.customDocDomain ?? '').trim() }), + }, + + transformResponse: async (_response, params) => ({ + success: true, + output: { + docId: String(params?.docId ?? '').trim(), + customDocDomain: String(params?.customDocDomain ?? '').trim(), + }, + }), + + outputs: { + docId: { type: 'string', description: 'ID of the doc' }, + customDocDomain: { type: 'string', description: 'The custom domain that was added' }, + }, + } diff --git a/apps/sim/tools/coda/add_permission.ts b/apps/sim/tools/coda/add_permission.ts new file mode 100644 index 00000000000..4ea78b6a6a1 --- /dev/null +++ b/apps/sim/tools/coda/add_permission.ts @@ -0,0 +1,107 @@ +import type { CodaAddPermissionParams, CodaAddPermissionResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + optionalTrimmed, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +const PRINCIPAL_FIELD = { + email: 'email', + group: 'groupId', + domain: 'domain', + workspace: 'workspaceId', +} as const + +export const codaAddPermissionTool: ToolConfig = + { + id: 'coda_add_permission', + name: 'Coda Share Doc', + description: + 'Share a Coda doc with a user, group, domain, workspace, or anyone with the link. Sharing with an email sends a notification unless suppressed.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + access: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Access level to grant: "readonly", "comment", or "write"', + }, + principalType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Who to share with: "email", "group", "domain", "workspace", or "anyone"', + }, + principal: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Email address, group ID, domain, or workspace ID matching principalType. Not used for "anyone".', + }, + suppressEmail: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Do not send a sharing notification email', + }, + }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'acl', 'permissions')), + method: 'POST', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const type = params.principalType + let principal: Record + if (type === 'anyone') { + principal = { type } + } else { + const field = Object.hasOwn(PRINCIPAL_FIELD, type) + ? PRINCIPAL_FIELD[type as keyof typeof PRINCIPAL_FIELD] + : undefined + if (!field) { + throw new Error('principalType must be one of: email, group, domain, workspace, anyone') + } + const value = optionalTrimmed(params.principal) + if (!value) throw new Error(`principal is required when principalType is "${type}"`) + principal = { type, [field]: value } + } + return { + access: params.access, + principal, + ...(typeof params.suppressEmail === 'boolean' + ? { suppressEmail: params.suppressEmail } + : {}), + } + }, + }, + + transformResponse: async (_response, params) => ({ + success: true, + output: { + docId: String(params?.docId ?? '').trim(), + access: params?.access ?? '', + principalType: params?.principalType ?? '', + }, + }), + + outputs: { + docId: { type: 'string', description: 'ID of the shared doc' }, + access: { type: 'string', description: 'Access level granted' }, + principalType: { type: 'string', description: 'Type of principal the doc was shared with' }, + }, + } diff --git a/apps/sim/tools/coda/change_user_role.ts b/apps/sim/tools/coda/change_user_role.ts new file mode 100644 index 00000000000..46251403548 --- /dev/null +++ b/apps/sim/tools/coda/change_user_role.ts @@ -0,0 +1,69 @@ +import type { CodaChangeUserRoleParams, CodaChangeUserRoleResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + codaPath, + WORKSPACE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaChangeUserRoleTool: ToolConfig< + CodaChangeUserRoleParams, + CodaChangeUserRoleResponse +> = { + id: 'coda_change_user_role', + name: 'Coda Change User Role', + description: + 'Change the workspace role of a Coda user. Requires Admin access in a workspace that belongs to an organization.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + workspaceId: WORKSPACE_ID_PARAM, + email: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Email address of the workspace member', + }, + newRole: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'New role: "Admin", "DocMaker", or "Editor"', + }, + }, + + request: { + url: (params) => + buildCodaUrl(codaPath('workspaces', [params.workspaceId, 'workspaceId'], 'users', 'role')), + method: 'POST', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => ({ email: String(params.email ?? '').trim(), newRole: params.newRole }), + }, + + transformResponse: async (response, params) => { + const data = (await response.json()) as { roleChangedAt: string } + return { + success: true, + output: { + email: String(params?.email ?? '').trim(), + newRole: params?.newRole ?? '', + roleChangedAt: data.roleChangedAt, + }, + } + }, + + outputs: { + email: { type: 'string', description: 'Email address of the member' }, + newRole: { type: 'string', description: 'Role assigned' }, + roleChangedAt: { type: 'string', description: 'When the role change took effect' }, + }, +} diff --git a/apps/sim/tools/coda/coda.live.test.ts b/apps/sim/tools/coda/coda.live.test.ts new file mode 100644 index 00000000000..9f5f01cbaa7 --- /dev/null +++ b/apps/sim/tools/coda/coda.live.test.ts @@ -0,0 +1,1135 @@ +/** + * Live end-to-end verification of the Coda integration against a real Coda account. + * + * Skipped unless `CODA_LIVE=1` and `CODA_API_TOKEN` are set, so it is inert in CI. Every + * operation runs the way a workflow run does: block field values go through the block's + * `tools.config.params`, then the real `executeTool` pipeline (request building, fetch, + * error extraction, `transformResponse`). Each output is checked against the tool's + * declared output schema. Selector attachments and the credential validator also run live. + * + * The suite only mutates resources it creates (a folder, a doc, and a copy of Coda's public + * API guide doc, whose button it pushes) and deletes them at the end. Sharing with an email + * address runs only when `CODA_LIVE_SHARE_EMAIL` is set; notifications are suppressed. + * + * CODA_LIVE=1 CODA_API_TOKEN=... ../../node_modules/.bin/vitest run tools/coda/coda.live.test.ts + * + * @vitest-environment node + */ +import { sleep } from '@sim/utils/helpers' +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' + +vi.unmock('@/tools/registry') + +import { validateCodaServiceAccount } from '@/lib/credentials/token-service-accounts/validators/coda' +import { createSelectorProtectedValues } from '@/lib/selectors/server/protected-values' +import { codaSelectorAttachments } from '@/lib/selectors/server/providers/coda' +import type { ExecuteServerSelectorArgs } from '@/lib/selectors/server/types' +import type { SelectorContext, SelectorRequest } from '@/lib/selectors/types' +import { CodaBlock } from '@/blocks/blocks/coda' +import { executeTool } from '@/tools' +import { tools as toolRegistry } from '@/tools/registry' +import type { OutputProperty } from '@/tools/types' + +const LIVE = process.env.CODA_LIVE === '1' && Boolean(process.env.CODA_API_TOKEN) +const token = process.env.CODA_API_TOKEN ?? '' +const shareEmail = process.env.CODA_LIVE_SHARE_EMAIL +const TIMEOUT = 300_000 + +interface RunResult { + success: boolean + output: Record + error?: string +} + +function log(label: string, value: unknown) { + const rendered = typeof value === 'string' ? value : JSON.stringify(value) + process.stdout.write(` [coda-live] ${label}: ${rendered?.slice(0, 1200)}\n`) +} + +/** Recursively checks a tool output against its declared output schema. */ +function schemaViolations( + value: unknown, + schema: OutputProperty, + path: string, + violations: string[] +): void { + if (value === null) { + if (!schema.nullable) violations.push(`${path}: null but not declared nullable`) + return + } + if (value === undefined) { + if (!schema.optional) violations.push(`${path}: missing but not declared optional`) + return + } + switch (schema.type) { + case 'string': + if (typeof value !== 'string') + violations.push(`${path}: expected string, got ${typeof value}`) + return + case 'number': + if (typeof value !== 'number') + violations.push(`${path}: expected number, got ${typeof value}`) + return + case 'boolean': + if (typeof value !== 'boolean') + violations.push(`${path}: expected boolean, got ${typeof value}`) + return + case 'array': { + if (!Array.isArray(value)) { + violations.push(`${path}: expected array`) + return + } + if (schema.items) { + value.forEach((item, index) => + schemaViolations(item, schema.items as OutputProperty, `${path}[${index}]`, violations) + ) + } + return + } + case 'object': { + if (typeof value !== 'object' || Array.isArray(value)) { + violations.push(`${path}: expected object`) + return + } + if (schema.properties) { + objectViolations(value as Record, schema.properties, path, violations) + } + return + } + default: + return + } +} + +function objectViolations( + value: Record, + properties: Record, + path: string, + violations: string[] +) { + for (const [key, property] of Object.entries(properties)) { + schemaViolations(value[key], property, `${path}.${key}`, violations) + } + for (const key of Object.keys(value)) { + if (!(key in properties)) violations.push(`${path}.${key}: not declared in outputs`) + } +} + +/** + * Runs one block operation exactly like the generic block handler: the serialized field + * values are merged with `tools.config.params`, and the selected tool runs through + * `executeTool` with the credential's resolved access token. + */ +async function run(values: Record): Promise { + const config = CodaBlock.tools.config! + const toolId = config.tool!(values) as string + const mapped = config.params ? config.params(values) : {} + const result = (await executeTool(toolId, { + ...values, + ...mapped, + accessToken: token, + })) as RunResult + if (result.success) { + const tool = toolRegistry[toolId] + const violations: string[] = [] + objectViolations(result.output, tool.outputs ?? {}, toolId, violations) + expect(violations, `${toolId} output schema`).toEqual([]) + } + log(`${values.operation}`, result.success ? result.output : `ERROR ${result.error}`) + return result +} + +async function runOk(values: Record): Promise> { + const result = await run(values) + expect(result.error, `${values.operation} failed`).toBeUndefined() + expect(result.success).toBe(true) + return result.output +} + +async function waitForMutation(requestId: string) { + for (let attempt = 0; attempt < 60; attempt++) { + const status = await runOk({ operation: 'get_mutation_status', mutationRequestId: requestId }) + if (status.completed) return status + await sleep(2_000) + } + throw new Error(`mutation ${requestId} did not complete`) +} + +/** New docs return 409 until Coda finishes provisioning them for the API. */ +async function waitForDocReady(docId: string) { + for (let attempt = 0; attempt < 60; attempt++) { + const result = await executeTool('coda_list_pages', { accessToken: token, docId }) + if (result.success) return + await sleep(2_000) + } + throw new Error(`doc ${docId} never became accessible`) +} + +async function waitFor(label: string, probe: () => Promise): Promise { + for (let attempt = 0; attempt < 45; attempt++) { + const value = await probe() + if (value !== undefined) return value + await sleep(2_000) + } + throw new Error(`timed out waiting for ${label}`) +} + +function selectorArgs( + selectorKey: ExecuteServerSelectorArgs['selectorKey'], + request: SelectorRequest, + context: SelectorContext = {} +): ExecuteServerSelectorArgs { + return { + selectorKey, + context: { oauthCredential: 'live-credential', ...context }, + request, + scope: { kind: 'workspace', workspaceId: 'workspace-live' }, + workspaceId: 'workspace-live', + principal: { kind: 'session', userId: 'user-live', sessionId: 'session-live' }, + requesterUserId: 'user-live', + credential: { suppliedId: 'live-credential', fixedToken: token, providerId: 'coda' }, + references: new Map(), + protectedValues: createSelectorProtectedValues(), + } +} + +const state: { + workspaceId?: string + loginId?: string + myDocsFolderId?: string + folderId?: string + docId?: string + docBrowserLink?: string + homePageId?: string + subPageId?: string + embedPageId?: string + syncPageId?: string + tableId?: string + nameColumnId?: string + statusColumnId?: string + rowIds: string[] + permissionId?: string + published?: boolean + copyDocId?: string +} = { rowIds: [] } + +describe.skipIf(!LIVE).sequential('coda live end-to-end', () => { + beforeAll(() => { + vi.unstubAllGlobals() + expect(vi.isMockFunction(globalThis.fetch)).toBe(false) + }) + + afterAll(async () => { + if (!LIVE) return + if (state.docId) + await executeTool('coda_delete_doc', { accessToken: token, docId: state.docId }) + if (state.copyDocId) { + await executeTool('coda_delete_doc', { accessToken: token, docId: state.copyDocId }) + } + if (state.folderId) { + await sleep(3_000) + await executeTool('coda_delete_folder', { accessToken: token, folderId: state.folderId }) + } + }, TIMEOUT) + + it( + 'validates the token like the credential connect flow', + async () => { + const result = await validateCodaServiceAccount({ apiToken: token }) + log('validator', result) + expect(result.principal).toMatchObject({ kind: 'user' }) + await expect( + validateCodaServiceAccount({ apiToken: 'not-a-real-token' }) + ).rejects.toMatchObject({ code: 'invalid_credentials' }) + }, + TIMEOUT + ) + + it( + 'reads the account, categories, and folders', + async () => { + const me = await runOk({ operation: 'whoami' }) + state.workspaceId = me.workspace.id + state.loginId = me.loginId + const categories = await runOk({ operation: 'list_categories' }) + expect(categories.categories.length).toBeGreaterThan(0) + const folders = await runOk({ operation: 'list_folders', limit: '50' }) + state.myDocsFolderId = folders.folders[0]?.id + expect(state.myDocsFolderId).toBeTruthy() + await runOk({ + operation: 'list_folders', + workspaceFilter: state.workspaceId, + starred: 'false', + }) + await runOk({ operation: 'get_folder', folderId: state.myDocsFolderId }) + await runOk({ operation: 'get_analytics_last_updated' }) + }, + TIMEOUT + ) + + it( + 'creates, updates, reads, and lists a folder', + async () => { + const created = await runOk({ + operation: 'create_folder', + workspaceId: state.workspaceId, + folderName: ' Sim Coda E2E ', + folderDescription: 'Created by the Sim live test', + }) + state.folderId = created.folder.id + expect(created.folder.name).toBe('Sim Coda E2E') + const updated = await runOk({ + operation: 'update_folder', + folderId: state.folderId, + folderName: 'Sim Coda E2E (renamed)', + folderDescription: '', + }) + expect(updated.folder.id).toBe(state.folderId) + const renamed = await waitFor('folder rename', async () => { + const read = await runOk({ operation: 'get_folder', folderId: state.folderId }) + return read.folder.name === 'Sim Coda E2E (renamed)' ? read.folder : undefined + }) + expect(renamed.description).toBe('Created by the Sim live test') + await runOk({ + operation: 'list_folder_children', + folderId: state.myDocsFolderId, + limit: '10', + }) + }, + TIMEOUT + ) + + it( + 'creates a doc with an initial HTML page and table, then reads and updates it', + async () => { + const created = await runOk({ + operation: 'create_doc', + docTitle: 'Sim Coda E2E Doc', + folderId: state.folderId, + timezone: 'America/Los_Angeles', + pageName: 'Home', + pageSubtitle: 'Live test home', + iconName: 'rocket', + pageType: 'canvas', + contentFormat: 'html', + pageContent: + '

Tasks

Intro paragraph

NameStatus
SeedOpen
', + }) + state.docId = created.doc.id + state.docBrowserLink = created.doc.browserLink + expect(created.doc.folder?.id).toBe(state.folderId) + await waitForDocReady(state.docId!) + + const doc = await runOk({ operation: 'get_doc', docId: state.docId }) + expect(doc.doc.name).toBe('Sim Coda E2E Doc') + await runOk({ operation: 'update_doc', docId: state.docId, docTitle: 'Sim Coda E2E Doc v2' }) + await waitFor('doc rename', async () => { + const again = await runOk({ operation: 'get_doc', docId: state.docId }) + return again.doc.name === 'Sim Coda E2E Doc v2' ? true : undefined + }) + await runOk({ operation: 'update_doc', docId: state.docId, iconName: 'rocket' }) + const listed = await runOk({ + operation: 'list_docs', + docSearch: 'Sim Coda E2E', + isOwner: true, + folderId: state.folderId, + workspaceFilter: state.workspaceId, + starred: 'any', + limit: '5', + }) + expect(listed.docs.map((d: { id: string }) => d.id)).toContain(state.docId) + await runOk({ operation: 'list_docs', sourceDoc: state.docId, inGallery: false }) + }, + TIMEOUT + ) + + it( + 'lists docs, pages, tables, and folders through the dropdown selectors', + async () => { + const docs = await codaSelectorAttachments['coda.docs'].execute( + selectorArgs('coda.docs', { kind: 'list', search: 'Sim Coda E2E' }) + ) + log('selector coda.docs', docs) + expect(docs.kind === 'list' && docs.items.some((item) => item.id === state.docId)).toBe(true) + const docDetail = await codaSelectorAttachments['coda.docs'].execute( + selectorArgs('coda.docs', { kind: 'detail', id: state.docId! }) + ) + expect(docDetail).toMatchObject({ kind: 'detail', item: { id: state.docId } }) + + const pages = await codaSelectorAttachments['coda.pages'].execute( + selectorArgs('coda.pages', { kind: 'list' }, { docId: state.docId }) + ) + log('selector coda.pages', pages) + expect(pages.kind === 'list' && pages.items.length).toBeGreaterThan(0) + if (pages.kind === 'list') state.homePageId = pages.items[0].id + + const tables = await codaSelectorAttachments['coda.tables'].execute( + selectorArgs('coda.tables', { kind: 'list' }, { docId: state.docId }) + ) + log('selector coda.tables', tables) + if (tables.kind === 'list') state.tableId = tables.items[0]?.id + expect(state.tableId).toBeTruthy() + + const folders = await codaSelectorAttachments['coda.folders'].execute( + selectorArgs('coda.folders', { kind: 'list' }) + ) + expect(folders.kind === 'list' && folders.items.some((f) => f.id === state.folderId)).toBe( + true + ) + await expect( + codaSelectorAttachments['coda.pages'].execute( + selectorArgs( + 'coda.pages', + { kind: 'detail', id: 'canvas-doesnotexist' }, + { + docId: state.docId, + } + ) + ) + ).resolves.toEqual({ kind: 'detail', item: null }) + }, + TIMEOUT + ) + + it( + 'creates, updates, reads, exports, and deletes pages and content', + async () => { + const home = await runOk({ + operation: 'get_page', + docId: state.docId, + pageId: state.homePageId, + }) + expect(home.page.subtitle).toBe('Live test home') + + const sub = await runOk({ + operation: 'create_page', + docId: state.docId, + pageName: 'Child page', + parentPageId: state.homePageId, + pageType: 'canvas', + contentFormat: 'markdown', + pageContent: '# Child\n\n- one\n- two', + pageSubtitle: 'child subtitle', + iconName: 'star', + }) + state.subPageId = sub.pageId + await waitForMutation(sub.requestId) + + const embed = await runOk({ + operation: 'create_page', + docId: state.docId, + pageName: 'Embed page', + pageType: 'embed', + embedUrl: 'https://example.com', + renderMethod: 'standard', + }) + state.embedPageId = embed.pageId + await waitForMutation(embed.requestId) + + const sync = await run({ + operation: 'create_page', + docId: state.docId, + pageName: 'Sync page', + pageType: 'syncPage', + syncSourceDocId: state.docId, + syncMode: 'page', + syncSourcePageId: state.subPageId, + includeSubpages: false, + }) + if (sync.success) { + state.syncPageId = sync.output.pageId + await waitForMutation(sync.output.requestId) + } + + const pages = await runOk({ operation: 'list_pages', docId: state.docId, limit: '50' }) + expect(pages.pages.map((p: { id: string }) => p.id)).toContain(state.subPageId) + const child = await runOk({ + operation: 'get_page', + docId: state.docId, + pageId: state.subPageId, + }) + expect(child.page.parent?.id).toBe(state.homePageId) + + const updated = await runOk({ + operation: 'update_page', + docId: state.docId, + pageId: state.subPageId, + pageName: 'Child page renamed', + pageSubtitle: 'new subtitle', + pageVisibility: 'unchanged', + insertionMode: 'append', + contentFormat: 'markdown', + pageContent: 'Appended paragraph', + }) + await waitForMutation(updated.requestId) + const renamedPage = await runOk({ + operation: 'get_page', + docId: state.docId, + pageId: state.subPageId, + }) + expect(renamedPage.page.name).toBe('Child page renamed') + expect(renamedPage.page.subtitle).toBe('new subtitle') + + const hide = await run({ + operation: 'update_page', + docId: state.docId, + pageId: state.subPageId, + pageVisibility: 'hidden', + }) + if (hide.success) { + await waitForMutation(hide.output.requestId) + } else { + expect(hide.error).toMatch(/plan/i) + } + + const content = await runOk({ + operation: 'get_page_content', + docId: state.docId, + pageId: state.subPageId, + limit: '100', + }) + expect( + content.items.some((i: { content: string }) => i.content === 'Appended paragraph') + ).toBe(true) + const target = content.items.find( + (i: { content: string }) => i.content === 'Appended paragraph' + ) + + const replaced = await runOk({ + operation: 'update_page', + docId: state.docId, + pageId: state.subPageId, + insertionMode: 'replace', + elementId: target.id, + contentFormat: 'html', + pageContent: '

Replaced paragraph

', + }) + await waitForMutation(replaced.requestId) + const afterReplace = await runOk({ + operation: 'get_page_content', + docId: state.docId, + pageId: state.subPageId, + }) + const replacedItem = afterReplace.items.find( + (i: { content: string }) => i.content === 'Replaced paragraph' + ) + expect(replacedItem).toBeTruthy() + + const guard = await run({ + operation: 'delete_page_content', + docId: state.docId, + pageId: state.subPageId, + }) + expect(guard.success).toBe(false) + expect(guard.error).toContain('deleteAll') + + const deletedOne = await runOk({ + operation: 'delete_page_content', + docId: state.docId, + pageId: state.subPageId, + elementIds: replacedItem.id, + }) + await waitForMutation(deletedOne.requestId) + + const exported = await runOk({ + operation: 'export_page', + docId: state.docId, + pageId: state.homePageId, + outputFormat: 'markdown', + }) + const finished = await waitFor('export', async () => { + const status = await runOk({ + operation: 'get_page_export_status', + docId: state.docId, + pageId: state.homePageId, + exportId: exported.exportId, + }) + return status.status === 'complete' || status.status === 'failed' ? status : undefined + }) + expect(finished.status).toBe('complete') + const markdown = await (await fetch(finished.downloadLink)).text() + log('export markdown', markdown) + expect(markdown).toContain('Tasks') + + const clearAll = await runOk({ + operation: 'delete_page_content', + docId: state.docId, + pageId: state.subPageId, + deleteAllContent: true, + }) + await waitForMutation(clearAll.requestId) + + const removed = await runOk({ + operation: 'delete_page', + docId: state.docId, + pageId: state.embedPageId, + }) + await waitForMutation(removed.requestId) + }, + TIMEOUT + ) + + it( + 'reads the table schema and inserts, upserts, updates, and deletes rows', + async () => { + await runOk({ + operation: 'list_tables', + docId: state.docId, + tableTypes: 'table', + listSortBy: 'name', + }) + const allTables = await runOk({ operation: 'list_tables', docId: state.docId }) + expect(allTables.tables.map((t: { id: string }) => t.id)).toContain(state.tableId) + await runOk({ + operation: 'get_table', + docId: state.docId, + tableId: state.tableId, + useUpdatedTableLayouts: true, + }) + const columns = await runOk({ + operation: 'list_columns', + docId: state.docId, + tableId: state.tableId, + visibleOnly: true, + }) + state.nameColumnId = columns.columns.find((c: { name: string }) => c.name === 'Name')?.id + state.statusColumnId = columns.columns.find((c: { name: string }) => c.name === 'Status')?.id + expect(state.nameColumnId && state.statusColumnId).toBeTruthy() + await runOk({ + operation: 'get_column', + docId: state.docId, + tableId: state.tableId, + columnId: state.nameColumnId, + }) + + const columnOptions = await codaSelectorAttachments['coda.columns'].execute( + selectorArgs( + 'coda.columns', + { kind: 'list' }, + { docId: state.docId, tableId: state.tableId } + ) + ) + log('selector coda.columns', columnOptions) + expect(columnOptions.kind === 'list' && columnOptions.items.length).toBeGreaterThanOrEqual(2) + + const inserted = await runOk({ + operation: 'upsert_rows', + docId: state.docId, + tableId: state.tableId, + rows: JSON.stringify([ + { Name: 'Alpha', Status: 'Open' }, + { cells: [{ column: state.nameColumnId, value: 'Beta' }] }, + ]), + }) + expect(inserted.addedRowIds).toHaveLength(2) + await waitForMutation(inserted.requestId) + + const upserted = await runOk({ + operation: 'upsert_rows', + docId: state.docId, + tableId: state.tableId, + rows: [{ Name: 'Alpha', Status: 'Done' }], + keyColumns: 'Name', + disableParsing: true, + }) + expect(upserted.addedRowIds).toEqual([]) + await waitForMutation(upserted.requestId) + + const rows = await runOk({ + operation: 'list_rows', + docId: state.docId, + tableId: state.tableId, + useColumnNames: true, + rowSortBy: 'updatedAt', + valueFormat: 'simpleWithArrays', + limit: '50', + }) + const alpha = rows.rows.find( + (r: { values: Record }) => r.values.Name === 'Alpha' + ) + expect(alpha?.values.Status).toBe('Done') + expect(rows.nextSyncToken).toBeTruthy() + state.rowIds = rows.rows.map((r: { id: string }) => r.id) + + const filtered = await runOk({ + operation: 'list_rows', + docId: state.docId, + tableId: state.tableId, + rowFilter: `"Name":"Beta"`, + visibleOnly: true, + }) + expect(filtered.rows).toHaveLength(1) + + const rowOptions = await codaSelectorAttachments['coda.rows'].execute( + selectorArgs('coda.rows', { kind: 'list' }, { docId: state.docId, tableId: state.tableId }) + ) + log('selector coda.rows', rowOptions) + expect(rowOptions.kind === 'list' && rowOptions.items.length).toBe(state.rowIds.length) + + const one = await runOk({ + operation: 'get_row', + docId: state.docId, + tableId: state.tableId, + rowId: alpha.id, + valueFormat: 'rich', + }) + expect(one.row.parentTable?.id).toBe(state.tableId) + + const updated = await runOk({ + operation: 'update_row', + docId: state.docId, + tableId: state.tableId, + rowId: alpha.id, + cells: '{"Status": "Blocked"}', + }) + await waitForMutation(updated.requestId) + const changed = await runOk({ + operation: 'list_rows', + docId: state.docId, + tableId: state.tableId, + syncToken: rows.nextSyncToken, + useColumnNames: true, + }) + log('rows changed since sync token', changed.rows.length) + + const button = await run({ + operation: 'push_button', + docId: state.docId, + tableId: state.tableId, + rowId: alpha.id, + columnId: state.nameColumnId, + }) + expect(button.success).toBe(false) + expect(button.error).not.toContain('[object Object]') + + const deletedOne = await runOk({ + operation: 'delete_row', + docId: state.docId, + tableId: state.tableId, + rowId: alpha.id, + }) + await waitForMutation(deletedOne.requestId) + const remaining = state.rowIds.filter((id) => id !== alpha.id) + const deletedMany = await runOk({ + operation: 'delete_rows', + docId: state.docId, + tableId: state.tableId, + rowIds: remaining.join(', '), + }) + expect(deletedMany.rowIds).toEqual(remaining) + await waitForMutation(deletedMany.requestId) + }, + TIMEOUT + ) + + it( + 'reads formulas and controls and surfaces missing ones as readable errors', + async () => { + const formulas = await runOk({ + operation: 'list_formulas', + docId: state.docId, + listSortBy: 'name', + }) + const controls = await runOk({ operation: 'list_controls', docId: state.docId }) + for (const formula of formulas.formulas) { + await runOk({ operation: 'get_formula', docId: state.docId, formulaId: formula.id }) + } + for (const control of controls.controls) { + await runOk({ operation: 'get_control', docId: state.docId, controlId: control.id }) + } + const missingFormula = await run({ + operation: 'get_formula', + docId: state.docId, + formulaId: 'f-missing', + }) + expect(missingFormula.success).toBe(false) + const missingControl = await run({ + operation: 'get_control', + docId: state.docId, + controlId: 'ctrl-missing', + }) + expect(missingControl.success).toBe(false) + const automation = await run({ + operation: 'trigger_automation', + docId: state.docId, + ruleId: 'grid-auto-missing', + payload: '{"hello":"world"}', + }) + expect(automation.success).toBe(false) + expect(automation.error).not.toContain('[object Object]') + for (const key of ['coda.formulas', 'coda.controls'] as const) { + const result = await codaSelectorAttachments[key].execute( + selectorArgs(key, { kind: 'list' }, { docId: state.docId }) + ) + expect(result.kind).toBe('list') + } + }, + TIMEOUT + ) + + it( + 'manages sharing settings and permissions', + async () => { + const metadata = await runOk({ operation: 'get_sharing_metadata', docId: state.docId }) + expect(metadata.canShare).toBe(true) + const before = await runOk({ operation: 'get_acl_settings', docId: state.docId }) + const flipped = await runOk({ + operation: 'update_acl_settings', + docId: state.docId, + allowCopying: before.allowCopying ? 'false' : 'true', + allowEditorsToChangePermissions: 'unchanged', + allowViewersToRequestEditing: 'unchanged', + }) + expect(flipped.allowCopying).toBe(!before.allowCopying) + expect(flipped.allowViewersToRequestEditing).toBe(before.allowViewersToRequestEditing) + await runOk({ + operation: 'update_acl_settings', + docId: state.docId, + allowCopying: before.allowCopying ? 'true' : 'false', + }) + + await runOk({ + operation: 'search_principals', + docId: state.docId, + principalQuery: state.loginId?.split('@')[0], + }) + + const anyone = await run({ + operation: 'add_permission', + docId: state.docId, + access: 'readonly', + principalType: 'anyone', + }) + if (!anyone.success) expect(anyone.error).toMatch(/limit/i) + if (shareEmail) { + const shared = await runOk({ + operation: 'add_permission', + docId: state.docId, + access: 'comment', + principalType: 'email', + principal: shareEmail, + suppressEmail: true, + }) + expect(shared).toMatchObject({ access: 'comment', principalType: 'email' }) + const permissions = await waitFor('permission', async () => { + const listed = await runOk({ + operation: 'list_permissions', + docId: state.docId, + limit: '20', + }) + return listed.permissions.find( + (p: { principal: { email?: string } }) => p.principal.email === shareEmail + ) + }) + expect(permissions.access).toBe('comment') + state.permissionId = permissions.id + + const permissionOptions = await codaSelectorAttachments['coda.permissions'].execute( + selectorArgs('coda.permissions', { kind: 'list' }, { docId: state.docId }) + ) + log('selector coda.permissions', permissionOptions) + + expect( + permissionOptions.kind === 'list' && + permissionOptions.items.some((item) => item.id === state.permissionId) + ).toBe(true) + await runOk({ + operation: 'delete_permission', + docId: state.docId, + permissionId: state.permissionId, + }) + await waitFor('permission removal', async () => { + const listed = await runOk({ operation: 'list_permissions', docId: state.docId }) + return listed.permissions.some((p: { id: string }) => p.id === state.permissionId) + ? undefined + : true + }) + } + const bad = await run({ + operation: 'add_permission', + docId: state.docId, + access: 'readonly', + principalType: 'email', + principal: ' ', + }) + expect(bad.success).toBe(false) + }, + TIMEOUT + ) + + it( + 'publishes, inspects custom domains, and unpublishes', + async () => { + const categories = await runOk({ operation: 'list_categories' }) + const publish = await run({ + operation: 'publish_doc', + docId: state.docId, + slug: `sim-coda-e2e-${Date.now()}`, + publishMode: 'view', + discoverable: 'false', + categoryNames: categories.categories[0], + }) + if (!publish.success) expect(publish.error).toMatch(/maker profile/i) + if (publish.success) { + state.published = true + await waitForMutation(publish.output.requestId) + const doc = await runOk({ operation: 'get_doc', docId: state.docId }) + log('published doc', doc.doc.published) + } + const domains = await runOk({ operation: 'list_custom_domains', docId: state.docId }) + expect(domains.nextPageToken).toBeNull() + const provider = await runOk({ + operation: 'get_custom_domain_provider', + customDocDomain: 'example.com', + }) + expect(provider.provider).toBeTruthy() + const add = await run({ + operation: 'add_custom_domain', + docId: state.docId, + customDocDomain: 'coda-e2e.sim-test.invalid', + }) + if (!add.success) expect(add.error).toMatch(/plan/i) + if (add.success) { + await run({ + operation: 'delete_custom_domain', + docId: state.docId, + customDocDomain: 'coda-e2e.sim-test.invalid', + }) + } + const unpublish = await run({ operation: 'unpublish_doc', docId: state.docId }) + if (state.published) expect(unpublish.success).toBe(true) + else if (!unpublish.success) expect(unpublish.error).not.toContain('[object Object]') + }, + TIMEOUT + ) + + it( + 'reads workspace membership, roles, and analytics', + async () => { + const members = await run({ + operation: 'list_workspace_members', + workspaceId: state.workspaceId, + }) + if (!members.success) expect(members.error).toMatch(/organization/i) + await run({ + operation: 'list_workspace_members', + workspaceId: state.workspaceId, + includedRoles: 'Admin, DocMaker', + }) + await run({ operation: 'list_workspace_roles', workspaceId: state.workspaceId }) + if (members.success) { + const me = members.output.members[0] + await run({ + operation: 'change_user_role', + workspaceId: state.workspaceId, + memberEmail: me.email, + newRole: me.role, + }) + } + await run({ + operation: 'list_doc_analytics', + docIds: state.docId, + sinceDate: '2026-01-01', + untilDate: '2026-12-31', + analyticsScale: 'cumulative', + analyticsOrderBy: 'views', + analyticsDirection: 'descending', + limit: '10', + }) + await run({ operation: 'list_doc_analytics', docSearch: 'Sim', isPublished: false }) + await run({ operation: 'list_page_analytics', docId: state.docId, sinceDate: '2026-01-01' }) + await run({ + operation: 'get_doc_analytics_summary', + sinceDate: '2026-01-01', + workspaceFilter: state.workspaceId, + }) + }, + TIMEOUT + ) + + it( + 'copies a doc with formulas, controls, views, and button columns and exercises them', + async () => { + const copy = await runOk({ + operation: 'create_doc', + docTitle: 'Sim Coda E2E Copy', + sourceDoc: 'BynGmkjg07', + folderId: state.folderId, + }) + state.copyDocId = copy.doc.id + expect(copy.doc.sourceDoc?.id).toBe('BynGmkjg07') + await waitForDocReady(state.copyDocId!) + + const copies = await runOk({ operation: 'list_docs', sourceDoc: 'BynGmkjg07' }) + expect(copies.docs.map((d: { id: string }) => d.id)).toContain(state.copyDocId) + + const formulas = await runOk({ + operation: 'list_formulas', + docId: state.copyDocId, + limit: '100', + }) + expect(formulas.formulas.length).toBeGreaterThan(0) + const formula = await runOk({ + operation: 'get_formula', + docId: state.copyDocId, + formulaId: formulas.formulas[0].id, + }) + expect(formula.formula.id).toBe(formulas.formulas[0].id) + const byName = await runOk({ + operation: 'get_formula', + docId: state.copyDocId, + formulaId: formulas.formulas[0].name, + }) + expect(byName.formula.id).toBe(formulas.formulas[0].id) + + const controls = await runOk({ + operation: 'list_controls', + docId: state.copyDocId, + listSortBy: 'name', + }) + expect(controls.controls.length).toBeGreaterThan(0) + for (const control of controls.controls) { + const detail = await runOk({ + operation: 'get_control', + docId: state.copyDocId, + controlId: control.id, + }) + expect(detail.control.controlType).toBeTruthy() + } + for (const key of ['coda.formulas', 'coda.controls'] as const) { + const options = await codaSelectorAttachments[key].execute( + selectorArgs(key, { kind: 'list' }, { docId: state.copyDocId }) + ) + log(`selector ${key}`, options) + expect(options.kind === 'list' && options.items.length).toBeGreaterThan(0) + } + + const views = await runOk({ + operation: 'list_tables', + docId: state.copyDocId, + tableTypes: 'view', + }) + expect(views.tables.length).toBeGreaterThan(0) + expect(views.tables.every((t: { tableType: string }) => t.tableType === 'view')).toBe(true) + const view = await runOk({ + operation: 'get_table', + docId: state.copyDocId, + tableId: views.tables[0].id, + }) + expect(view.table.parentTable?.id).toBeTruthy() + const tableOptions = await codaSelectorAttachments['coda.tables'].execute( + selectorArgs('coda.tables', { kind: 'list' }, { docId: state.copyDocId }) + ) + expect( + tableOptions.kind === 'list' && + tableOptions.items.some((item) => item.label.endsWith('(view)')) + ).toBe(true) + + const calendar = await runOk({ + operation: 'list_tables', + docId: state.copyDocId, + tableTypes: 'table', + }) + const calendarTable = calendar.tables.find((t: { name: string }) => t.name === 'My Calendar') + const richRows = await runOk({ + operation: 'list_rows', + docId: state.copyDocId, + tableId: calendarTable.id, + valueFormat: 'rich', + rowSortBy: 'natural', + limit: '5', + }) + log('rich calendar rows', richRows.rows.slice(0, 2)) + const calendarColumns = await runOk({ + operation: 'list_columns', + docId: state.copyDocId, + tableId: 'My Calendar', + }) + expect( + calendarColumns.columns.some((c: { format: { type: string } }) => c.format.type === 'date') + ).toBe(true) + + const tasks = calendar.tables.find((t: { name: string }) => t.name === 'Tasks') + const taskColumns = await runOk({ + operation: 'list_columns', + docId: state.copyDocId, + tableId: tasks.id, + }) + const buttonColumn = taskColumns.columns.find( + (c: { format: { type: string } }) => c.format.type === 'button' + ) + const buttonDetail = await runOk({ + operation: 'get_column', + docId: state.copyDocId, + tableId: tasks.id, + columnId: buttonColumn.id, + }) + expect(buttonDetail.column.format.type).toBe('button') + const taskRows = await runOk({ + operation: 'list_rows', + docId: state.copyDocId, + tableId: tasks.id, + limit: '1', + }) + const rowOptions = await codaSelectorAttachments['coda.rows'].execute( + selectorArgs( + 'coda.rows', + { kind: 'detail', id: taskRows.rows[0].id }, + { + docId: state.copyDocId, + tableId: tasks.id, + } + ) + ) + expect(rowOptions).toMatchObject({ kind: 'detail', item: { id: taskRows.rows[0].id } }) + const pushed = await runOk({ + operation: 'push_button', + docId: state.copyDocId, + tableId: tasks.id, + rowId: taskRows.rows[0].id, + columnId: buttonColumn.id, + }) + expect(pushed).toMatchObject({ rowId: taskRows.rows[0].id, columnId: buttonColumn.id }) + await waitForMutation(pushed.requestId) + + const pagesCopy = await runOk({ operation: 'list_pages', docId: state.copyDocId, limit: '3' }) + if (pagesCopy.nextPageToken) { + const next = await runOk({ + operation: 'list_pages', + docId: state.copyDocId, + limit: '3', + pageToken: pagesCopy.nextPageToken, + }) + expect(next.pages[0]?.id).not.toBe(pagesCopy.pages[0]?.id) + } + + await runOk({ operation: 'delete_doc', docId: state.copyDocId }) + state.copyDocId = undefined + }, + TIMEOUT + ) + + it( + 'resolves browser links and cleans up', + async () => { + const resolved = await runOk({ + operation: 'resolve_browser_link', + browserUrl: state.docBrowserLink, + }) + expect(resolved.resource.id).toBe(state.docId) + await runOk({ + operation: 'resolve_browser_link', + browserUrl: state.docBrowserLink, + degradeGracefully: true, + }) + + const deleted = await runOk({ operation: 'delete_doc', docId: state.docId }) + expect(deleted.docId).toBe(state.docId) + state.docId = undefined + await sleep(5_000) + const folder = await run({ operation: 'delete_folder', folderId: state.folderId }) + if (folder.success) state.folderId = undefined + }, + TIMEOUT + ) +}) diff --git a/apps/sim/tools/coda/coda.test.ts b/apps/sim/tools/coda/coda.test.ts new file mode 100644 index 00000000000..c618bfd41d9 --- /dev/null +++ b/apps/sim/tools/coda/coda.test.ts @@ -0,0 +1,424 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { CodaBlock } from '@/blocks/blocks/coda' +import * as codaTools from '@/tools/coda' +import { codaAddPermissionTool } from '@/tools/coda/add_permission' +import { codaCreateDocTool } from '@/tools/coda/create_doc' +import { codaCreatePageTool } from '@/tools/coda/create_page' +import { codaDeletePageContentTool } from '@/tools/coda/delete_page_content' +import { codaDeleteRowsTool } from '@/tools/coda/delete_rows' +import { codaListDocsTool } from '@/tools/coda/list_docs' +import { codaListRowsTool } from '@/tools/coda/list_rows' +import { codaPublishDocTool } from '@/tools/coda/publish_doc' +import { codaResolveBrowserLinkTool } from '@/tools/coda/resolve_browser_link' +import { codaUpdateAclSettingsTool } from '@/tools/coda/update_acl_settings' +import { codaUpdatePageTool } from '@/tools/coda/update_page' +import { codaUpdateRowTool } from '@/tools/coda/update_row' +import { codaUpsertRowsTool } from '@/tools/coda/upsert_rows' +import { buildCodaUrl, CODA_FIELD_UPDATE_RETRY, CODA_RETRY } from '@/tools/coda/utils' +import { codaWhoamiTool } from '@/tools/coda/whoami' +import { ErrorExtractorId, extractErrorMessageWithId } from '@/tools/error-extractors' +import type { OutputProperty } from '@/tools/types' + +const table = { accessToken: 'token', docId: 'AbCDeFGH', tableId: 'grid-pqRst-U' } + +/** Lists output paths a tool returned as null whose schema does not declare `nullable`. */ +function findUndeclaredNulls( + value: unknown, + properties: Record | undefined, + path: string +): string[] { + if (!properties || value === null || typeof value !== 'object') return [] + return Object.entries(properties).flatMap(([key, schema]) => { + const child = (value as Record)[key] + const childPath = `${path}.${key}` + if (child === null) return schema.nullable ? [] : [childPath] + if (Array.isArray(child)) { + return child.flatMap((item) => findUndeclaredNulls(item, schema.items?.properties, childPath)) + } + return findUndeclaredNulls(child, schema.properties, childPath) + }) +} + +function resolveUrl

(url: string | ((params: P) => string), params: P): string { + return typeof url === 'function' ? url(params) : url +} + +describe('Coda request URLs', () => { + it('encodes path segments and omits unset query params', () => { + const url = resolveUrl(codaListRowsTool.request.url, { + ...table, + tableId: 'My Table', + query: '"Status":"Done"', + useColumnNames: true, + limit: 10, + }) + expect(url).toBe( + 'https://coda.io/apis/v1/docs/AbCDeFGH/tables/My%20Table/rows?query=%22Status%22%3A%22Done%22&useColumnNames=true&limit=10' + ) + }) + + it('rejects path traversal in resource identifiers', () => { + expect(() => resolveUrl(codaListRowsTool.request.url, { ...table, tableId: '..' })).toThrow( + 'path traversal' + ) + }) + + it('rejects a blank browser link instead of sending no url', () => { + expect(() => + resolveUrl(codaResolveBrowserLinkTool.request.url, { accessToken: 'token', url: ' ' }) + ).toThrow('url is required') + }) + + it('builds list docs URLs without a doc path', () => { + expect( + resolveUrl(codaListDocsTool.request.url, { + accessToken: 'token', + query: 'Roadmap', + isOwner: true, + }) + ).toBe('https://coda.io/apis/v1/docs?query=Roadmap&isOwner=true') + }) +}) + +describe('Coda row bodies', () => { + it('converts column maps to cells and parses key columns', () => { + const body = codaUpsertRowsTool.request.body!({ + ...table, + rows: JSON.stringify([{ 'c-name': 'Apple', 'c-price': 1.25 }]), + keyColumns: 'c-name, c-sku', + }) + expect(body).toEqual({ + rows: [ + { + cells: [ + { column: 'c-name', value: 'Apple' }, + { column: 'c-price', value: 1.25 }, + ], + }, + ], + keyColumns: ['c-name', 'c-sku'], + }) + }) + + it('passes through rows already in Coda cell format', () => { + const cells = [{ column: 'c-name', value: 'Pear' }] + expect(codaUpsertRowsTool.request.body!({ ...table, rows: [{ cells }] })).toEqual({ + rows: [{ cells }], + }) + }) + + it('maps a column named cells instead of reading it as the cells wrapper', () => { + expect( + codaUpdateRowTool.request.body!({ + ...table, + rowId: 'i-1', + cells: { cells: 'x', Status: 'Done' }, + }) + ).toEqual({ + row: { + cells: [ + { column: 'cells', value: 'x' }, + { column: 'Status', value: 'Done' }, + ], + }, + }) + expect( + codaUpdateRowTool.request.body!({ ...table, rowId: 'i-1', cells: { cells: 'x' } }) + ).toEqual({ row: { cells: [{ column: 'cells', value: 'x' }] } }) + }) + + it('rejects an empty upsert', () => { + expect(() => codaUpsertRowsTool.request.body!({ ...table, rows: '[]' })).toThrow( + 'at least one row' + ) + }) + + it('wraps row updates in a row object', () => { + expect( + codaUpdateRowTool.request.body!({ ...table, rowId: 'i-1', cells: { Status: 'Done' } }) + ).toEqual({ row: { cells: [{ column: 'Status', value: 'Done' }] } }) + }) + + it('accepts row IDs as a JSON array string', () => { + expect(codaDeleteRowsTool.request.body!({ ...table, rowIds: '["i-1", "i-2"]' })).toEqual({ + rowIds: ['i-1', 'i-2'], + }) + }) +}) + +describe('Coda page and permission bodies', () => { + const page = { accessToken: 'token', docId: 'AbCDeFGH', pageId: 'canvas-1' } + + it('requires an insertion mode when updating content', () => { + expect(() => codaUpdatePageTool.request.body!({ ...page, content: '# Hi' })).toThrow( + 'insertionMode' + ) + }) + + it('builds a content update with a default markdown format', () => { + expect( + codaUpdatePageTool.request.body!({ ...page, content: '# Hi', insertionMode: 'append' }) + ).toEqual({ + contentUpdate: { + insertionMode: 'append', + canvasContent: { format: 'markdown', content: '# Hi' }, + }, + }) + }) + + it('maps each principal type to its field', () => { + const base = { accessToken: 'token', docId: 'AbCDeFGH', access: 'write' as const } + expect( + codaAddPermissionTool.request.body!({ ...base, principalType: 'group', principal: 'grp-1' }) + ).toEqual({ access: 'write', principal: { type: 'group', groupId: 'grp-1' } }) + expect(codaAddPermissionTool.request.body!({ ...base, principalType: 'anyone' })).toEqual({ + access: 'write', + principal: { type: 'anyone' }, + }) + expect(() => + codaAddPermissionTool.request.body!({ ...base, principalType: 'email', principal: ' ' }) + ).toThrow('principal is required') + }) +}) + +describe('Coda block params', () => { + const mapParams = CodaBlock.tools.config!.params! + + it('maps operation-specific inputs onto tool params', () => { + const result = mapParams({ + operation: 'list_rows', + rowFilter: '"Status":"Done"', + docSearch: 'ignored', + rowSortBy: 'updatedAt', + limit: '50', + useColumnNames: true, + visibleOnly: false, + }) + expect(result).toMatchObject({ + query: '"Status":"Done"', + sortBy: 'updatedAt', + limit: 50, + useColumnNames: true, + visibleOnly: undefined, + }) + }) + + it('routes shared params by operation and maps page visibility', () => { + expect( + mapParams({ + operation: 'get_mutation_status', + mutationRequestId: 'req-1', + workspaceFilter: 'ws-f', + }) + ).toMatchObject({ requestId: 'req-1', workspaceId: 'ws-f' }) + expect( + mapParams({ operation: 'create_folder', folderName: 'Plans', workspaceId: 'ws-1' }) + ).toMatchObject({ name: 'Plans', workspaceId: 'ws-1' }) + expect(mapParams({ operation: 'update_page', pageVisibility: 'hidden' })).toMatchObject({ + isHidden: true, + }) + expect(mapParams({ operation: 'update_page', pageVisibility: 'unchanged' })).toMatchObject({ + isHidden: undefined, + }) + }) + + it('selects the tool from the operation', () => { + expect(CodaBlock.tools.config!.tool!({ operation: 'upsert_rows' })).toBe('coda_upsert_rows') + }) +}) + +describe('Coda page content and publishing bodies', () => { + it('builds embed and sync page content', () => { + expect( + codaCreatePageTool.request.body!({ + accessToken: 'token', + docId: 'AbCDeFGH', + pageType: 'embed', + embedUrl: ' https://example.com ', + }) + ).toEqual({ pageContent: { type: 'embed', url: 'https://example.com' } }) + expect( + codaCreatePageTool.request.body!({ + accessToken: 'token', + docId: 'AbCDeFGH', + pageType: 'syncPage', + sourceDocId: 'src', + syncMode: 'document', + }) + ).toEqual({ pageContent: { type: 'syncPage', mode: 'document', sourceDocId: 'src' } }) + expect(() => + codaCreatePageTool.request.body!({ + accessToken: 'token', + docId: 'AbCDeFGH', + pageType: 'syncPage', + sourceDocId: 'src', + }) + ).toThrow('sourcePageId') + }) + + it('nests initial page settings when creating a doc', () => { + expect( + codaCreateDocTool.request.body!({ + accessToken: 'token', + title: 'Plan', + pageName: 'Overview', + content: '# Hi', + }) + ).toEqual({ + title: 'Plan', + initialPage: { + name: 'Overview', + pageContent: { type: 'canvas', canvasContent: { format: 'markdown', content: '# Hi' } }, + }, + }) + }) + + it('only sends explicitly set sharing settings', () => { + expect( + codaUpdateAclSettingsTool.request.body!({ + accessToken: 'token', + docId: 'AbCDeFGH', + allowCopying: false, + }) + ).toEqual({ allowCopying: false }) + expect(() => + codaUpdateAclSettingsTool.request.body!({ accessToken: 'token', docId: 'AbCDeFGH' }) + ).toThrow('at least one') + }) + + it('splits publish categories and omits unset fields', () => { + expect( + codaPublishDocTool.request.body!({ + accessToken: 'token', + docId: 'AbCDeFGH', + categoryNames: 'Project management, Engineering', + mode: 'view', + }) + ).toEqual({ categoryNames: ['Project management', 'Engineering'], mode: 'view' }) + }) + + it('sends the bearer token from the resolved credential', () => { + expect(codaWhoamiTool.request.headers({ accessToken: 'secret' })).toEqual({ + Authorization: 'Bearer secret', + Accept: 'application/json', + }) + expect(codaWhoamiTool.oauth).toEqual({ required: true, provider: 'coda' }) + }) +}) + +describe('Coda delete page content', () => { + const page = { accessToken: 'token', docId: 'AbCDeFGH', pageId: 'canvas-1' } + + it('never clears a whole page without an explicit deleteAll', () => { + expect(() => codaDeletePageContentTool.request.body!({ ...page })).toThrow('deleteAll') + expect(codaDeletePageContentTool.request.body!({ ...page, deleteAll: true })).toEqual({}) + expect( + codaDeletePageContentTool.request.body!({ + ...page, + elementIds: 'cl-1, cl-2', + deleteAll: true, + }) + ).toEqual({ elementIds: ['cl-1', 'cl-2'] }) + }) +}) + +describe('Coda pagination and errors', () => { + it('sends only the page token when continuing a list', () => { + expect( + buildCodaUrl('/docs/doc/tables/grid/rows', { + limit: 10, + useColumnNames: true, + pageToken: 'eyJsaW1pd', + }) + ).toBe('https://coda.io/apis/v1/docs/doc/tables/grid/rows?pageToken=eyJsaW1pd') + expect(buildCodaUrl('/docs', { limit: 10, pageToken: ' ' })).toBe( + 'https://coda.io/apis/v1/docs?limit=10' + ) + }) + + it('surfaces Coda schema validation detail instead of a generic Bad Request', () => { + const data = { + statusCode: 400, + statusMessage: 'Bad Request', + message: 'Bad Request', + codaType: 'RequestSchemaValidationFailed', + codaDetail: { + issues: [ + { + code: 'invalid_union', + errors: [ + [{ code: 'custom', message: 'Invalid pageToken', path: ['pageToken'] }], + [ + { + code: 'unrecognized_keys', + keys: ['limit'], + path: [], + message: 'Unrecognized key: "limit"', + }, + ], + ], + path: [], + message: 'Invalid input', + }, + ], + }, + } + expect(extractErrorMessageWithId({ status: 400, data }, ErrorExtractorId.CODA_ERRORS)).toBe( + 'Bad Request: pageToken: Invalid pageToken; Unrecognized key: "limit"' + ) + expect( + extractErrorMessageWithId( + { status: 404, data: { statusMessage: 'Not Found', message: 'Doc has been deleted.' } }, + ErrorExtractorId.CODA_ERRORS + ) + ).toBe('Doc has been deleted.') + }) +}) + +describe('Coda tool registration', () => { + const allTools = Object.entries(codaTools).filter(([name]) => name.endsWith('Tool')) + + it('exposes all 60 tools through the barrel', () => { + expect(allTools).toHaveLength(60) + }) + + it.each(allTools)( + '%s declares every output it can return as null as nullable', + async (_, tool) => { + const config = tool as { + outputs: Record + transformResponse: (response: Response, params: object) => Promise<{ output: unknown }> + } + const sparseBody = { + items: [{ doc: {}, page: {}, metrics: [{}] }], + customDocDomains: [{}], + resource: {}, + id: 'x', + } + const { output } = await config.transformResponse( + new Response(JSON.stringify(sparseBody)), + table + ) + expect(findUndeclaredNulls(output, config.outputs, '')).toEqual([]) + } + ) + + it.each(allTools)( + '%s retries safely repeatable calls and authenticates with the Coda credential', + (_, tool) => { + const config = tool as { + request: { method: unknown; retry?: unknown } + oauth?: unknown + params: Record + } + expect(config.request.retry).toBe( + config.request.method === 'PATCH' ? CODA_FIELD_UPDATE_RETRY : CODA_RETRY + ) + expect(config.oauth).toEqual({ required: true, provider: 'coda' }) + expect(config.params.accessToken).toMatchObject({ required: true, visibility: 'hidden' }) + } + ) +}) diff --git a/apps/sim/tools/coda/create_doc.ts b/apps/sim/tools/coda/create_doc.ts new file mode 100644 index 00000000000..ac7669cf00d --- /dev/null +++ b/apps/sim/tools/coda/create_doc.ts @@ -0,0 +1,171 @@ +import type { CodaCreateDocParams, CodaCreateDocResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + buildPageCreateContent, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + DOC_PROPERTIES, + mapDoc, + optionalTrimmed, + type RawCodaDoc, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaCreateDocTool: ToolConfig = { + id: 'coda_create_doc', + name: 'Coda Create Doc', + description: + 'Create a Coda doc, optionally copying an existing doc and setting up its first page with Markdown, HTML, an embed, or a sync page. Requires Doc Maker access in the workspace.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + title: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Title of the new doc (defaults to "Untitled")', + }, + sourceDoc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'ID of an existing doc to copy', + }, + timezone: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Timezone for the new doc (e.g., "America/Los_Angeles")', + }, + folderId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'ID of the folder to create the doc in (defaults to "My docs")', + }, + pageName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name of the initial page', + }, + pageSubtitle: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Subtitle of the initial page', + }, + iconName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Icon name for the initial page (e.g., "rocket")', + }, + imageUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cover image URL for the initial page', + }, + pageType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Initial page content type: "canvas" (default), "embed", or "syncPage"', + }, + contentFormat: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Canvas content format: "markdown" (default) or "html"', + }, + content: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Canvas content for the initial page in the chosen format', + }, + embedUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL to embed as a full page (pageType "embed")', + }, + renderMethod: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Embed render method: "standard" or "compatibility"', + }, + sourceDocId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Doc to sync from (pageType "syncPage")', + }, + sourcePageId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page to sync (pageType "syncPage" with syncMode "page")', + }, + syncMode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sync page mode: "page" (default) or "document"', + }, + includeSubpages: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Include subpages in a single-page sync page', + }, + }, + + request: { + url: () => buildCodaUrl('/docs'), + method: 'POST', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const pageContent = buildPageCreateContent(params) + const initialPage = { + ...(optionalTrimmed(params.pageName) ? { name: optionalTrimmed(params.pageName) } : {}), + ...(params.pageSubtitle ? { subtitle: params.pageSubtitle } : {}), + ...(optionalTrimmed(params.iconName) ? { iconName: optionalTrimmed(params.iconName) } : {}), + ...(optionalTrimmed(params.imageUrl) ? { imageUrl: optionalTrimmed(params.imageUrl) } : {}), + ...(pageContent ? { pageContent } : {}), + } + return { + ...(optionalTrimmed(params.title) ? { title: optionalTrimmed(params.title) } : {}), + ...(optionalTrimmed(params.sourceDoc) + ? { sourceDoc: optionalTrimmed(params.sourceDoc) } + : {}), + ...(optionalTrimmed(params.timezone) ? { timezone: optionalTrimmed(params.timezone) } : {}), + ...(optionalTrimmed(params.folderId) ? { folderId: optionalTrimmed(params.folderId) } : {}), + ...(Object.keys(initialPage).length > 0 ? { initialPage } : {}), + } + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaDoc & { requestId?: string } + return { success: true, output: { doc: mapDoc(data), requestId: data.requestId ?? null } } + }, + + outputs: { + doc: { type: 'object', description: 'The created doc', properties: DOC_PROPERTIES }, + requestId: { + type: 'string', + description: 'Coda request ID for the doc creation', + nullable: true, + }, + }, +} diff --git a/apps/sim/tools/coda/create_folder.ts b/apps/sim/tools/coda/create_folder.ts new file mode 100644 index 00000000000..c0bc25577d4 --- /dev/null +++ b/apps/sim/tools/coda/create_folder.ts @@ -0,0 +1,62 @@ +import type { CodaCreateFolderParams, CodaFolderResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + FOLDER_PROPERTIES, + mapFolder, + optionalTrimmed, + type RawCodaFolder, + WORKSPACE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaCreateFolderTool: ToolConfig = { + id: 'coda_create_folder', + name: 'Coda Create Folder', + description: 'Create a folder in a Coda workspace', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the folder', + }, + workspaceId: WORKSPACE_ID_PARAM, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Description of the folder', + }, + }, + + request: { + url: () => buildCodaUrl('/folders'), + method: 'POST', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => ({ + name: String(params.name ?? '').trim(), + workspaceId: String(params.workspaceId ?? '').trim(), + ...(optionalTrimmed(params.description) ? { description: params.description } : {}), + }), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaFolder + return { success: true, output: { folder: mapFolder(data) } } + }, + + outputs: { + folder: { type: 'object', description: 'The created folder', properties: FOLDER_PROPERTIES }, + }, +} diff --git a/apps/sim/tools/coda/create_page.ts b/apps/sim/tools/coda/create_page.ts new file mode 100644 index 00000000000..7308c349be1 --- /dev/null +++ b/apps/sim/tools/coda/create_page.ts @@ -0,0 +1,144 @@ +import type { CodaCreatePageParams, CodaPageMutationResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + buildPageCreateContent, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + optionalTrimmed, + REQUEST_ID_OUTPUT, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaCreatePageTool: ToolConfig = { + id: 'coda_create_page', + name: 'Coda Create Page', + description: + 'Create a page in a Coda doc, optionally as a subpage, with Markdown or HTML content, a full-page embed, or a sync page from another doc. The page is created asynchronously. Requires Doc Maker access.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name of the page', + }, + subtitle: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Subtitle of the page', + }, + iconName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name of the page icon (e.g., "rocket")', + }, + imageUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL of a cover image for the page', + }, + parentPageId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'ID of the parent page, to create this page as a subpage', + }, + pageType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page content type: "canvas" (default), "embed", or "syncPage"', + }, + contentFormat: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Canvas content format: "markdown" (default) or "html"', + }, + content: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Canvas page content in the chosen format', + }, + embedUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL to embed as a full page (pageType "embed")', + }, + renderMethod: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Embed render method: "standard" or "compatibility"', + }, + sourceDocId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Doc to sync from (pageType "syncPage")', + }, + sourcePageId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page to sync (pageType "syncPage" with syncMode "page")', + }, + syncMode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sync page mode: "page" (default) or "document"', + }, + includeSubpages: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Include subpages in a single-page sync page', + }, + }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'pages')), + method: 'POST', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const pageContent = buildPageCreateContent(params) + return { + ...(optionalTrimmed(params.name) ? { name: optionalTrimmed(params.name) } : {}), + ...(params.subtitle ? { subtitle: params.subtitle } : {}), + ...(optionalTrimmed(params.iconName) ? { iconName: optionalTrimmed(params.iconName) } : {}), + ...(optionalTrimmed(params.imageUrl) ? { imageUrl: optionalTrimmed(params.imageUrl) } : {}), + ...(optionalTrimmed(params.parentPageId) + ? { parentPageId: optionalTrimmed(params.parentPageId) } + : {}), + ...(pageContent ? { pageContent } : {}), + } + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string; id: string } + return { success: true, output: { requestId: data.requestId, pageId: data.id } } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + pageId: { type: 'string', description: 'ID of the created page' }, + }, +} diff --git a/apps/sim/tools/coda/delete_custom_domain.ts b/apps/sim/tools/coda/delete_custom_domain.ts new file mode 100644 index 00000000000..efb18baac18 --- /dev/null +++ b/apps/sim/tools/coda/delete_custom_domain.ts @@ -0,0 +1,50 @@ +import type { CodaCustomDomainParams, CodaCustomDomainResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + CUSTOM_DOMAIN_PARAM, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaDeleteCustomDomainTool: ToolConfig< + CodaCustomDomainParams, + CodaCustomDomainResponse +> = { + id: 'coda_delete_custom_domain', + name: 'Coda Delete Custom Domain', + description: 'Remove a custom domain from a published Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM, customDocDomain: CUSTOM_DOMAIN_PARAM }, + + request: { + url: (params) => + buildCodaUrl( + codaDocPath(params.docId, 'domains', [params.customDocDomain, 'customDocDomain']) + ), + method: 'DELETE', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (_response, params) => ({ + success: true, + output: { + docId: String(params?.docId ?? '').trim(), + customDocDomain: String(params?.customDocDomain ?? '').trim(), + }, + }), + + outputs: { + docId: { type: 'string', description: 'ID of the doc' }, + customDocDomain: { type: 'string', description: 'The custom domain that was removed' }, + }, +} diff --git a/apps/sim/tools/coda/delete_doc.ts b/apps/sim/tools/coda/delete_doc.ts new file mode 100644 index 00000000000..8a1b0d9d668 --- /dev/null +++ b/apps/sim/tools/coda/delete_doc.ts @@ -0,0 +1,39 @@ +import type { CodaDocIdResponse, CodaDocParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaDeleteDocTool: ToolConfig = { + id: 'coda_delete_doc', + name: 'Coda Delete Doc', + description: 'Delete a Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId)), + method: 'DELETE', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (_response, params) => ({ + success: true, + output: { docId: String(params?.docId ?? '').trim() }, + }), + + outputs: { + docId: { type: 'string', description: 'ID of the deleted doc' }, + }, +} diff --git a/apps/sim/tools/coda/delete_folder.ts b/apps/sim/tools/coda/delete_folder.ts new file mode 100644 index 00000000000..20eb2bc6e20 --- /dev/null +++ b/apps/sim/tools/coda/delete_folder.ts @@ -0,0 +1,39 @@ +import type { CodaDeleteFolderResponse, CodaFolderParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + codaPath, + FOLDER_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaDeleteFolderTool: ToolConfig = { + id: 'coda_delete_folder', + name: 'Coda Delete Folder', + description: 'Delete an empty Coda folder (it must contain no docs)', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, folderId: FOLDER_ID_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaPath('folders', [params.folderId, 'folderId'])), + method: 'DELETE', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (_response, params) => ({ + success: true, + output: { folderId: String(params?.folderId ?? '').trim() }, + }), + + outputs: { + folderId: { type: 'string', description: 'ID of the deleted folder' }, + }, +} diff --git a/apps/sim/tools/coda/delete_page.ts b/apps/sim/tools/coda/delete_page.ts new file mode 100644 index 00000000000..6db84298b33 --- /dev/null +++ b/apps/sim/tools/coda/delete_page.ts @@ -0,0 +1,42 @@ +import type { CodaPageMutationResponse, CodaPageParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + PAGE_ID_PARAM, + REQUEST_ID_OUTPUT, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaDeletePageTool: ToolConfig = { + id: 'coda_delete_page', + name: 'Coda Delete Page', + description: 'Delete a page from a Coda doc. Applied asynchronously.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM, pageId: PAGE_ID_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'pages', [params.pageId, 'pageId'])), + method: 'DELETE', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string; id: string } + return { success: true, output: { requestId: data.requestId, pageId: data.id } } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + pageId: { type: 'string', description: 'ID of the deleted page' }, + }, +} diff --git a/apps/sim/tools/coda/delete_page_content.ts b/apps/sim/tools/coda/delete_page_content.ts new file mode 100644 index 00000000000..020325aa1f8 --- /dev/null +++ b/apps/sim/tools/coda/delete_page_content.ts @@ -0,0 +1,73 @@ +import type { CodaDeletePageContentParams, CodaPageMutationResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + PAGE_ID_PARAM, + parseStringList, + REQUEST_ID_OUTPUT, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaDeletePageContentTool: ToolConfig< + CodaDeletePageContentParams, + CodaPageMutationResponse +> = { + id: 'coda_delete_page_content', + name: 'Coda Delete Page Content', + description: + 'Delete specific content elements from a Coda page, or all of its content when no element IDs are given. Applied asynchronously.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + pageId: PAGE_ID_PARAM, + elementIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Element IDs to delete (from Get Page Content), as an array or comma-separated list', + }, + deleteAll: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Set to true, with no element IDs, to delete all content from the page', + }, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'pages', [params.pageId, 'pageId'], 'content')), + method: 'DELETE', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const elementIds = parseStringList(params.elementIds, 'elementIds') + if (elementIds.length > 0) return { elementIds } + if (params.deleteAll !== true) { + throw new Error('Provide elementIds, or set deleteAll to true to delete all page content') + } + return {} + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string; id: string } + return { success: true, output: { requestId: data.requestId, pageId: data.id } } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + pageId: { type: 'string', description: 'ID of the page whose content was deleted' }, + }, +} diff --git a/apps/sim/tools/coda/delete_permission.ts b/apps/sim/tools/coda/delete_permission.ts new file mode 100644 index 00000000000..52682da97bc --- /dev/null +++ b/apps/sim/tools/coda/delete_permission.ts @@ -0,0 +1,58 @@ +import type { CodaDeletePermissionParams, CodaDeletePermissionResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaDeletePermissionTool: ToolConfig< + CodaDeletePermissionParams, + CodaDeletePermissionResponse +> = { + id: 'coda_delete_permission', + name: 'Coda Remove Permission', + description: 'Revoke a sharing permission on a Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + permissionId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the permission to remove (from List Permissions)', + }, + }, + + request: { + url: (params) => + buildCodaUrl( + codaDocPath(params.docId, 'acl', 'permissions', [params.permissionId, 'permissionId']) + ), + method: 'DELETE', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (_response, params) => ({ + success: true, + output: { + docId: String(params?.docId ?? '').trim(), + permissionId: String(params?.permissionId ?? '').trim(), + }, + }), + + outputs: { + docId: { type: 'string', description: 'ID of the doc' }, + permissionId: { type: 'string', description: 'ID of the removed permission' }, + }, +} diff --git a/apps/sim/tools/coda/delete_row.ts b/apps/sim/tools/coda/delete_row.ts new file mode 100644 index 00000000000..8861092e66c --- /dev/null +++ b/apps/sim/tools/coda/delete_row.ts @@ -0,0 +1,49 @@ +import type { CodaRowMutationResponse, CodaRowParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + REQUEST_ID_OUTPUT, + ROW_ID_PARAM, + TABLE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaDeleteRowTool: ToolConfig = { + id: 'coda_delete_row', + name: 'Coda Delete Row', + description: 'Delete a row from a Coda table or view. Applied asynchronously.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM, tableId: TABLE_ID_PARAM, rowId: ROW_ID_PARAM }, + + request: { + url: (params) => + buildCodaUrl( + codaDocPath(params.docId, 'tables', [params.tableId, 'tableId'], 'rows', [ + params.rowId, + 'rowId', + ]) + ), + method: 'DELETE', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string; id: string } + return { success: true, output: { requestId: data.requestId, rowId: data.id } } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + rowId: { type: 'string', description: 'ID of the deleted row' }, + }, +} diff --git a/apps/sim/tools/coda/delete_rows.ts b/apps/sim/tools/coda/delete_rows.ts new file mode 100644 index 00000000000..29ce1bb5a2e --- /dev/null +++ b/apps/sim/tools/coda/delete_rows.ts @@ -0,0 +1,64 @@ +import type { CodaDeleteRowsParams, CodaDeleteRowsResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + parseStringList, + REQUEST_ID_OUTPUT, + TABLE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaDeleteRowsTool: ToolConfig = { + id: 'coda_delete_rows', + name: 'Coda Delete Rows', + description: 'Delete multiple rows from a Coda table or view by ID. Applied asynchronously.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + tableId: TABLE_ID_PARAM, + rowIds: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'Row IDs to delete, as an array or comma-separated list (e.g., ["i-bCdeFgh", "i-CdEfgHi"])', + }, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'tables', [params.tableId, 'tableId'], 'rows')), + method: 'DELETE', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const rowIds = parseStringList(params.rowIds, 'rowIds') + if (rowIds.length === 0) throw new Error('rowIds must contain at least one row ID') + return { rowIds } + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string; rowIds?: string[] } + return { success: true, output: { requestId: data.requestId, rowIds: data.rowIds ?? [] } } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + rowIds: { + type: 'array', + description: 'IDs of the rows queued for deletion', + items: { type: 'string', description: 'Row ID' }, + }, + }, +} diff --git a/apps/sim/tools/coda/export_page.ts b/apps/sim/tools/coda/export_page.ts new file mode 100644 index 00000000000..4f78d348636 --- /dev/null +++ b/apps/sim/tools/coda/export_page.ts @@ -0,0 +1,55 @@ +import type { CodaExportPageParams, CodaExportPageResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + PAGE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaExportPageTool: ToolConfig = { + id: 'coda_export_page', + name: 'Coda Export Page', + description: + 'Start exporting a Coda page as HTML or Markdown. Poll Get Page Export Status with the returned export ID for the download link.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + pageId: PAGE_ID_PARAM, + outputFormat: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Export format: "markdown" or "html"', + }, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'pages', [params.pageId, 'pageId'], 'export')), + method: 'POST', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => ({ outputFormat: params.outputFormat }), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { id: string; status: string; href: string } + return { success: true, output: { exportId: data.id, status: data.status, href: data.href } } + }, + + outputs: { + exportId: { type: 'string', description: 'ID of the export request' }, + status: { type: 'string', description: 'Export status (inProgress, failed, complete)' }, + href: { type: 'string', description: 'API link that reports the export status' }, + }, +} diff --git a/apps/sim/tools/coda/get_acl_settings.ts b/apps/sim/tools/coda/get_acl_settings.ts new file mode 100644 index 00000000000..fe4df28451e --- /dev/null +++ b/apps/sim/tools/coda/get_acl_settings.ts @@ -0,0 +1,45 @@ +import type { CodaAclSettingsResponse, CodaDocParams } from '@/tools/coda/types' +import { + ACL_SETTINGS_OUTPUTS, + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetAclSettingsTool: ToolConfig = { + id: 'coda_get_acl_settings', + name: 'Coda Get Sharing Settings', + description: 'Get the sharing settings of a Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'acl', 'settings')), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as CodaAclSettingsResponse['output'] + return { + success: true, + output: { + allowEditorsToChangePermissions: data.allowEditorsToChangePermissions, + allowCopying: data.allowCopying, + allowViewersToRequestEditing: data.allowViewersToRequestEditing, + }, + } + }, + + outputs: ACL_SETTINGS_OUTPUTS, +} diff --git a/apps/sim/tools/coda/get_analytics_last_updated.ts b/apps/sim/tools/coda/get_analytics_last_updated.ts new file mode 100644 index 00000000000..d7bf9ba6431 --- /dev/null +++ b/apps/sim/tools/coda/get_analytics_last_updated.ts @@ -0,0 +1,53 @@ +import type { CodaAnalyticsLastUpdatedResponse, CodaAuthParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetAnalyticsLastUpdatedTool: ToolConfig< + CodaAuthParams, + CodaAnalyticsLastUpdatedResponse +> = { + id: 'coda_get_analytics_last_updated', + name: 'Coda Get Analytics Last Updated', + description: + 'Get the dates (Pacific time) Coda analytics were last refreshed, to know how current analytics data is', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams }, + + request: { + url: () => buildCodaUrl('/analytics/updated'), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as CodaAnalyticsLastUpdatedResponse['output'] + return { + success: true, + output: { + docAnalyticsLastUpdated: data.docAnalyticsLastUpdated, + packAnalyticsLastUpdated: data.packAnalyticsLastUpdated, + packFormulaAnalyticsLastUpdated: data.packFormulaAnalyticsLastUpdated, + }, + } + }, + + outputs: { + docAnalyticsLastUpdated: { type: 'string', description: 'Date doc analytics last updated' }, + packAnalyticsLastUpdated: { type: 'string', description: 'Date Pack analytics last updated' }, + packFormulaAnalyticsLastUpdated: { + type: 'string', + description: 'Date Pack formula analytics last updated', + }, + }, +} diff --git a/apps/sim/tools/coda/get_column.ts b/apps/sim/tools/coda/get_column.ts new file mode 100644 index 00000000000..fa1cf7cfc10 --- /dev/null +++ b/apps/sim/tools/coda/get_column.ts @@ -0,0 +1,59 @@ +import type { CodaColumnResponse, CodaGetColumnParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + COLUMN_PROPERTIES, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + mapColumn, + type RawCodaColumn, + TABLE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetColumnTool: ToolConfig = { + id: 'coda_get_column', + name: 'Coda Get Column', + description: 'Get details about a column in a Coda table, including its full format settings', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + tableId: TABLE_ID_PARAM, + columnId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID or name of the column (IDs are recommended, e.g., "c-tuVwxYz")', + }, + }, + + request: { + url: (params) => + buildCodaUrl( + codaDocPath(params.docId, 'tables', [params.tableId, 'tableId'], 'columns', [ + params.columnId, + 'columnId', + ]) + ), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaColumn + return { success: true, output: { column: mapColumn(data) } } + }, + + outputs: { + column: { type: 'object', description: 'Column details', properties: COLUMN_PROPERTIES }, + }, +} diff --git a/apps/sim/tools/coda/get_control.ts b/apps/sim/tools/coda/get_control.ts new file mode 100644 index 00000000000..80e364e8d55 --- /dev/null +++ b/apps/sim/tools/coda/get_control.ts @@ -0,0 +1,72 @@ +import type { CodaControlResponse, CodaGetControlParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + mapControl, + NAMED_REFERENCE_PROPERTIES, + type RawCodaNamedReference, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetControlTool: ToolConfig = { + id: 'coda_get_control', + name: 'Coda Get Control', + description: 'Get the type and current value of a control in a Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + controlId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID or name of the control (IDs are recommended, e.g., "ctrl-cDefGhij")', + }, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'controls', [params.controlId, 'controlId'])), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaNamedReference & { + controlType?: string + value?: unknown + } + return { success: true, output: { control: mapControl(data) } } + }, + + outputs: { + control: { + type: 'object', + description: 'Control details', + properties: { + ...NAMED_REFERENCE_PROPERTIES, + controlType: { + type: 'string', + description: + 'Control type (aiBlock, button, checkbox, datePicker, dateRangePicker, dateTimePicker, lookup, multiselect, select, scale, slider, reaction, textbox, timePicker)', + nullable: true, + }, + value: { + type: 'json', + description: 'Current value (string, number, boolean, or array of these)', + nullable: true, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/coda/get_custom_domain_provider.ts b/apps/sim/tools/coda/get_custom_domain_provider.ts new file mode 100644 index 00000000000..ec601098d9a --- /dev/null +++ b/apps/sim/tools/coda/get_custom_domain_provider.ts @@ -0,0 +1,54 @@ +import type { + CodaGetCustomDomainProviderParams, + CodaGetCustomDomainProviderResponse, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + CUSTOM_DOMAIN_PARAM, + codaAuthParams, + codaHeaders, + codaOAuth, + codaPath, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetCustomDomainProviderTool: ToolConfig< + CodaGetCustomDomainProviderParams, + CodaGetCustomDomainProviderResponse +> = { + id: 'coda_get_custom_domain_provider', + name: 'Coda Get Custom Domain Provider', + description: + 'Look up the DNS provider (GoDaddy, Namecheap, Hover, Network Solutions, Google Domains, or Other) of a custom domain', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, customDocDomain: CUSTOM_DOMAIN_PARAM }, + + request: { + url: (params) => + buildCodaUrl(codaPath('domains', 'provider', [params.customDocDomain, 'customDocDomain'])), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response, params) => { + const data = (await response.json()) as { provider: string } + return { + success: true, + output: { + customDocDomain: String(params?.customDocDomain ?? '').trim(), + provider: data.provider, + }, + } + }, + + outputs: { + customDocDomain: { type: 'string', description: 'The custom domain' }, + provider: { type: 'string', description: 'DNS provider of the domain' }, + }, +} diff --git a/apps/sim/tools/coda/get_doc.ts b/apps/sim/tools/coda/get_doc.ts new file mode 100644 index 00000000000..56b82aada5a --- /dev/null +++ b/apps/sim/tools/coda/get_doc.ts @@ -0,0 +1,43 @@ +import type { CodaDocParams, CodaDocResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + DOC_PROPERTIES, + mapDoc, + type RawCodaDoc, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetDocTool: ToolConfig = { + id: 'coda_get_doc', + name: 'Coda Get Doc', + description: + 'Get metadata for a Coda doc, including its owner, workspace, folder, size, and publishing settings', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId)), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaDoc + return { success: true, output: { doc: mapDoc(data) } } + }, + + outputs: { + doc: { type: 'object', description: 'Doc metadata', properties: DOC_PROPERTIES }, + }, +} diff --git a/apps/sim/tools/coda/get_doc_analytics_summary.ts b/apps/sim/tools/coda/get_doc_analytics_summary.ts new file mode 100644 index 00000000000..82543342c68 --- /dev/null +++ b/apps/sim/tools/coda/get_doc_analytics_summary.ts @@ -0,0 +1,76 @@ +import type { + CodaDocAnalyticsSummaryParams, + CodaDocAnalyticsSummaryResponse, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + optionalTrimmed, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetDocAnalyticsSummaryTool: ToolConfig< + CodaDocAnalyticsSummaryParams, + CodaDocAnalyticsSummaryResponse +> = { + id: 'coda_get_doc_analytics_summary', + name: 'Coda Get Doc Analytics Summary', + description: 'Get the total number of sessions across the Coda docs the user can access', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + isPublished: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Only include published docs', + }, + sinceDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include activity on or after this date (YYYY-MM-DD)', + }, + untilDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include activity on or before this date (YYYY-MM-DD)', + }, + workspaceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include docs in this workspace', + }, + }, + + request: { + url: (params) => + buildCodaUrl('/analytics/docs/summary', { + isPublished: params.isPublished, + sinceDate: optionalTrimmed(params.sinceDate), + untilDate: optionalTrimmed(params.untilDate), + workspaceId: optionalTrimmed(params.workspaceId), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { totalSessions: number } + return { success: true, output: { totalSessions: data.totalSessions } } + }, + + outputs: { + totalSessions: { type: 'number', description: 'Total sessions across all matching docs' }, + }, +} diff --git a/apps/sim/tools/coda/get_folder.ts b/apps/sim/tools/coda/get_folder.ts new file mode 100644 index 00000000000..7a3db5aace6 --- /dev/null +++ b/apps/sim/tools/coda/get_folder.ts @@ -0,0 +1,42 @@ +import type { CodaFolderParams, CodaFolderResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + codaPath, + FOLDER_ID_PARAM, + FOLDER_PROPERTIES, + mapFolder, + type RawCodaFolder, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetFolderTool: ToolConfig = { + id: 'coda_get_folder', + name: 'Coda Get Folder', + description: 'Get details about a Coda folder', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, folderId: FOLDER_ID_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaPath('folders', [params.folderId, 'folderId'])), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaFolder + return { success: true, output: { folder: mapFolder(data) } } + }, + + outputs: { + folder: { type: 'object', description: 'Folder details', properties: FOLDER_PROPERTIES }, + }, +} diff --git a/apps/sim/tools/coda/get_formula.ts b/apps/sim/tools/coda/get_formula.ts new file mode 100644 index 00000000000..3f66c9fc83c --- /dev/null +++ b/apps/sim/tools/coda/get_formula.ts @@ -0,0 +1,63 @@ +import type { CodaFormulaResponse, CodaGetFormulaParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + mapFormula, + NAMED_REFERENCE_PROPERTIES, + type RawCodaNamedReference, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetFormulaTool: ToolConfig = { + id: 'coda_get_formula', + name: 'Coda Get Formula', + description: 'Get the current computed value of a named formula in a Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + formulaId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID or name of the formula (IDs are recommended, e.g., "f-fgHijkLm")', + }, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'formulas', [params.formulaId, 'formulaId'])), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaNamedReference & { value?: unknown } + return { success: true, output: { formula: mapFormula(data) } } + }, + + outputs: { + formula: { + type: 'object', + description: 'Formula details', + properties: { + ...NAMED_REFERENCE_PROPERTIES, + value: { + type: 'json', + description: 'Computed value (string, number, boolean, or array of these)', + nullable: true, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/coda/get_mutation_status.ts b/apps/sim/tools/coda/get_mutation_status.ts new file mode 100644 index 00000000000..99fdc4354a2 --- /dev/null +++ b/apps/sim/tools/coda/get_mutation_status.ts @@ -0,0 +1,55 @@ +import type { CodaGetMutationStatusParams, CodaGetMutationStatusResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + codaPath, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetMutationStatusTool: ToolConfig< + CodaGetMutationStatusParams, + CodaGetMutationStatusResponse +> = { + id: 'coda_get_mutation_status', + name: 'Coda Get Mutation Status', + description: + 'Check whether a queued Coda change (row, page, publish, or automation request) has been applied. Status is kept for about a day.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + requestId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Request ID returned by a Coda write operation', + }, + }, + + request: { + url: (params) => buildCodaUrl(codaPath('mutationStatus', [params.requestId, 'requestId'])), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { completed: boolean; warning?: string } + return { success: true, output: { completed: data.completed, warning: data.warning ?? null } } + }, + + outputs: { + completed: { type: 'boolean', description: 'Whether the change has been applied' }, + warning: { + type: 'string', + description: 'Warning if the change completed with caveats', + nullable: true, + }, + }, +} diff --git a/apps/sim/tools/coda/get_page.ts b/apps/sim/tools/coda/get_page.ts new file mode 100644 index 00000000000..0a8e48625de --- /dev/null +++ b/apps/sim/tools/coda/get_page.ts @@ -0,0 +1,43 @@ +import type { CodaPageParams, CodaPageResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + mapPage, + PAGE_ID_PARAM, + PAGE_PROPERTIES, + type RawCodaPage, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetPageTool: ToolConfig = { + id: 'coda_get_page', + name: 'Coda Get Page', + description: 'Get metadata for a page in a Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM, pageId: PAGE_ID_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'pages', [params.pageId, 'pageId'])), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaPage + return { success: true, output: { page: mapPage(data) } } + }, + + outputs: { + page: { type: 'object', description: 'Page metadata', properties: PAGE_PROPERTIES }, + }, +} diff --git a/apps/sim/tools/coda/get_page_content.ts b/apps/sim/tools/coda/get_page_content.ts new file mode 100644 index 00000000000..686fe4cf034 --- /dev/null +++ b/apps/sim/tools/coda/get_page_content.ts @@ -0,0 +1,110 @@ +import type { + CodaGetPageContentParams, + CodaGetPageContentResponse, + CodaPageContentItem, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_ID_PARAM, + PAGE_TOKEN_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +interface RawPageContentItem { + id: string + type: string + itemContent?: { style?: string; format?: string; content?: string; lineLevel?: number } +} + +export const codaGetPageContentTool: ToolConfig< + CodaGetPageContentParams, + CodaGetPageContentResponse +> = { + id: 'coda_get_page_content', + name: 'Coda Get Page Content', + description: + 'Read the content of a Coda canvas page as plain-text lines with their styles (headings, paragraphs, lists, quotes, code) and element IDs', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + pageId: PAGE_ID_PARAM, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of content items to return (1-500, default 50)', + }, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'pages', [params.pageId, 'pageId'], 'content'), { + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + items?: RawPageContentItem[] + nextPageToken?: string + } + const items: CodaPageContentItem[] = (data.items ?? []).map((item) => ({ + id: item.id, + type: item.type, + style: item.itemContent?.style ?? null, + format: item.itemContent?.format ?? null, + content: item.itemContent?.content ?? null, + lineLevel: item.itemContent?.lineLevel ?? null, + })) + return { success: true, output: { items, nextPageToken: data.nextPageToken || null } } + }, + + outputs: { + items: { + type: 'array', + description: 'Content elements on the page, in order', + items: { + type: 'object', + properties: { + id: { + type: 'string', + description: 'Element ID, usable with Update Page and Delete Page Content', + }, + type: { type: 'string', description: 'Element type (line)' }, + style: { + type: 'string', + description: + 'Line style (paragraph, h1, h2, h3, bulletedList, numberedList, checkboxList, collapsibleList, blockQuote, pullQuote, code)', + nullable: true, + }, + format: { type: 'string', description: 'Content format (plainText)', nullable: true }, + content: { type: 'string', description: 'Element text', nullable: true }, + lineLevel: { + type: 'number', + description: 'Indentation level for paragraphs, quotes, and list items', + nullable: true, + }, + }, + }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/get_page_export_status.ts b/apps/sim/tools/coda/get_page_export_status.ts new file mode 100644 index 00000000000..c218548e512 --- /dev/null +++ b/apps/sim/tools/coda/get_page_export_status.ts @@ -0,0 +1,90 @@ +import type { + CodaGetPageExportStatusParams, + CodaPageExportStatusResponse, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + PAGE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetPageExportStatusTool: ToolConfig< + CodaGetPageExportStatusParams, + CodaPageExportStatusResponse +> = { + id: 'coda_get_page_export_status', + name: 'Coda Get Page Export Status', + description: + 'Check a Coda page export and get its download link once complete. Download links expire shortly after they are issued.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + pageId: PAGE_ID_PARAM, + exportId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Export ID returned by Export Page', + }, + }, + + request: { + url: (params) => + buildCodaUrl( + codaDocPath(params.docId, 'pages', [params.pageId, 'pageId'], 'export', [ + params.exportId, + 'exportId', + ]) + ), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + id: string + status: string + href: string + downloadLink?: string + error?: string + } + return { + success: true, + output: { + exportId: data.id, + status: data.status, + href: data.href, + downloadLink: data.downloadLink ?? null, + exportError: data.error ?? null, + }, + } + }, + + outputs: { + exportId: { type: 'string', description: 'ID of the export request' }, + status: { type: 'string', description: 'Export status (inProgress, failed, complete)' }, + href: { type: 'string', description: 'API link that reports the export status' }, + downloadLink: { + type: 'string', + description: 'Short-lived download link for the exported file, once complete', + nullable: true, + }, + exportError: { + type: 'string', + description: 'Error message if the export failed', + nullable: true, + }, + }, +} diff --git a/apps/sim/tools/coda/get_row.ts b/apps/sim/tools/coda/get_row.ts new file mode 100644 index 00000000000..750230327e0 --- /dev/null +++ b/apps/sim/tools/coda/get_row.ts @@ -0,0 +1,68 @@ +import type { CodaGetRowParams, CodaRowResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + mapRow, + type RawCodaRow, + ROW_ID_PARAM, + ROW_PROPERTIES, + TABLE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetRowTool: ToolConfig = { + id: 'coda_get_row', + name: 'Coda Get Row', + description: 'Get a single row from a Coda table, including all of its cell values', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + tableId: TABLE_ID_PARAM, + rowId: ROW_ID_PARAM, + useColumnNames: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Key cell values by column name instead of column ID', + }, + valueFormat: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cell value format: "simple" (default), "simpleWithArrays", or "rich"', + }, + }, + + request: { + url: (params) => + buildCodaUrl( + codaDocPath(params.docId, 'tables', [params.tableId, 'tableId'], 'rows', [ + params.rowId, + 'rowId', + ]), + { useColumnNames: params.useColumnNames, valueFormat: params.valueFormat } + ), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaRow + return { success: true, output: { row: mapRow(data) } } + }, + + outputs: { + row: { type: 'object', description: 'Row details and values', properties: ROW_PROPERTIES }, + }, +} diff --git a/apps/sim/tools/coda/get_sharing_metadata.ts b/apps/sim/tools/coda/get_sharing_metadata.ts new file mode 100644 index 00000000000..f4722b22cf3 --- /dev/null +++ b/apps/sim/tools/coda/get_sharing_metadata.ts @@ -0,0 +1,57 @@ +import type { CodaDocParams, CodaSharingMetadataResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetSharingMetadataTool: ToolConfig = { + id: 'coda_get_sharing_metadata', + name: 'Coda Get Sharing Metadata', + description: + 'Check whether the connected user can share or copy a Coda doc, and whether they can share it with the workspace or organization', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'acl', 'metadata')), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as CodaSharingMetadataResponse['output'] + return { + success: true, + output: { + canShare: data.canShare, + canShareWithWorkspace: data.canShareWithWorkspace, + canShareWithOrg: data.canShareWithOrg, + canCopy: data.canCopy, + }, + } + }, + + outputs: { + canShare: { type: 'boolean', description: 'Whether the user can share the doc' }, + canShareWithWorkspace: { + type: 'boolean', + description: 'Whether the user can share the doc with the workspace', + }, + canShareWithOrg: { + type: 'boolean', + description: 'Whether the user can share the doc with the organization', + }, + canCopy: { type: 'boolean', description: 'Whether the user can copy the doc' }, + }, +} diff --git a/apps/sim/tools/coda/get_table.ts b/apps/sim/tools/coda/get_table.ts new file mode 100644 index 00000000000..82dbf132270 --- /dev/null +++ b/apps/sim/tools/coda/get_table.ts @@ -0,0 +1,58 @@ +import type { CodaGetTableParams, CodaTableResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + mapTable, + type RawCodaTable, + TABLE_ID_PARAM, + TABLE_PROPERTIES, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaGetTableTool: ToolConfig = { + id: 'coda_get_table', + name: 'Coda Get Table', + description: + 'Get details about a table or view in a Coda doc, including its row count, sorts, layout, and filter', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + tableId: TABLE_ID_PARAM, + useUpdatedTableLayouts: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: + 'Report detail and form layouts as "detail" and "form" instead of "masterDetail" for both', + }, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'tables', [params.tableId, 'tableId']), { + useUpdatedTableLayouts: params.useUpdatedTableLayouts, + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaTable + return { success: true, output: { table: mapTable(data) } } + }, + + outputs: { + table: { type: 'object', description: 'Table details', properties: TABLE_PROPERTIES }, + }, +} diff --git a/apps/sim/tools/coda/index.ts b/apps/sim/tools/coda/index.ts new file mode 100644 index 00000000000..585386d8954 --- /dev/null +++ b/apps/sim/tools/coda/index.ts @@ -0,0 +1,125 @@ +import { codaAddCustomDomainTool } from '@/tools/coda/add_custom_domain' +import { codaAddPermissionTool } from '@/tools/coda/add_permission' +import { codaChangeUserRoleTool } from '@/tools/coda/change_user_role' +import { codaCreateDocTool } from '@/tools/coda/create_doc' +import { codaCreateFolderTool } from '@/tools/coda/create_folder' +import { codaCreatePageTool } from '@/tools/coda/create_page' +import { codaDeleteCustomDomainTool } from '@/tools/coda/delete_custom_domain' +import { codaDeleteDocTool } from '@/tools/coda/delete_doc' +import { codaDeleteFolderTool } from '@/tools/coda/delete_folder' +import { codaDeletePageTool } from '@/tools/coda/delete_page' +import { codaDeletePageContentTool } from '@/tools/coda/delete_page_content' +import { codaDeletePermissionTool } from '@/tools/coda/delete_permission' +import { codaDeleteRowTool } from '@/tools/coda/delete_row' +import { codaDeleteRowsTool } from '@/tools/coda/delete_rows' +import { codaExportPageTool } from '@/tools/coda/export_page' +import { codaGetAclSettingsTool } from '@/tools/coda/get_acl_settings' +import { codaGetAnalyticsLastUpdatedTool } from '@/tools/coda/get_analytics_last_updated' +import { codaGetColumnTool } from '@/tools/coda/get_column' +import { codaGetControlTool } from '@/tools/coda/get_control' +import { codaGetCustomDomainProviderTool } from '@/tools/coda/get_custom_domain_provider' +import { codaGetDocTool } from '@/tools/coda/get_doc' +import { codaGetDocAnalyticsSummaryTool } from '@/tools/coda/get_doc_analytics_summary' +import { codaGetFolderTool } from '@/tools/coda/get_folder' +import { codaGetFormulaTool } from '@/tools/coda/get_formula' +import { codaGetMutationStatusTool } from '@/tools/coda/get_mutation_status' +import { codaGetPageTool } from '@/tools/coda/get_page' +import { codaGetPageContentTool } from '@/tools/coda/get_page_content' +import { codaGetPageExportStatusTool } from '@/tools/coda/get_page_export_status' +import { codaGetRowTool } from '@/tools/coda/get_row' +import { codaGetSharingMetadataTool } from '@/tools/coda/get_sharing_metadata' +import { codaGetTableTool } from '@/tools/coda/get_table' +import { codaListCategoriesTool } from '@/tools/coda/list_categories' +import { codaListColumnsTool } from '@/tools/coda/list_columns' +import { codaListControlsTool } from '@/tools/coda/list_controls' +import { codaListCustomDomainsTool } from '@/tools/coda/list_custom_domains' +import { codaListDocAnalyticsTool } from '@/tools/coda/list_doc_analytics' +import { codaListDocsTool } from '@/tools/coda/list_docs' +import { codaListFolderChildrenTool } from '@/tools/coda/list_folder_children' +import { codaListFoldersTool } from '@/tools/coda/list_folders' +import { codaListFormulasTool } from '@/tools/coda/list_formulas' +import { codaListPageAnalyticsTool } from '@/tools/coda/list_page_analytics' +import { codaListPagesTool } from '@/tools/coda/list_pages' +import { codaListPermissionsTool } from '@/tools/coda/list_permissions' +import { codaListRowsTool } from '@/tools/coda/list_rows' +import { codaListTablesTool } from '@/tools/coda/list_tables' +import { codaListWorkspaceMembersTool } from '@/tools/coda/list_workspace_members' +import { codaListWorkspaceRolesTool } from '@/tools/coda/list_workspace_roles' +import { codaPublishDocTool } from '@/tools/coda/publish_doc' +import { codaPushButtonTool } from '@/tools/coda/push_button' +import { codaResolveBrowserLinkTool } from '@/tools/coda/resolve_browser_link' +import { codaSearchPrincipalsTool } from '@/tools/coda/search_principals' +import { codaTriggerAutomationTool } from '@/tools/coda/trigger_automation' +import { codaUnpublishDocTool } from '@/tools/coda/unpublish_doc' +import { codaUpdateAclSettingsTool } from '@/tools/coda/update_acl_settings' +import { codaUpdateDocTool } from '@/tools/coda/update_doc' +import { codaUpdateFolderTool } from '@/tools/coda/update_folder' +import { codaUpdatePageTool } from '@/tools/coda/update_page' +import { codaUpdateRowTool } from '@/tools/coda/update_row' +import { codaUpsertRowsTool } from '@/tools/coda/upsert_rows' +import { codaWhoamiTool } from '@/tools/coda/whoami' + +export { + codaAddCustomDomainTool, + codaAddPermissionTool, + codaChangeUserRoleTool, + codaCreateDocTool, + codaCreateFolderTool, + codaCreatePageTool, + codaDeleteCustomDomainTool, + codaDeleteDocTool, + codaDeleteFolderTool, + codaDeletePageContentTool, + codaDeletePageTool, + codaDeletePermissionTool, + codaDeleteRowTool, + codaDeleteRowsTool, + codaExportPageTool, + codaGetAclSettingsTool, + codaGetAnalyticsLastUpdatedTool, + codaGetColumnTool, + codaGetControlTool, + codaGetCustomDomainProviderTool, + codaGetDocAnalyticsSummaryTool, + codaGetDocTool, + codaGetFolderTool, + codaGetFormulaTool, + codaGetMutationStatusTool, + codaGetPageContentTool, + codaGetPageExportStatusTool, + codaGetPageTool, + codaGetRowTool, + codaGetSharingMetadataTool, + codaGetTableTool, + codaListCategoriesTool, + codaListColumnsTool, + codaListControlsTool, + codaListCustomDomainsTool, + codaListDocAnalyticsTool, + codaListDocsTool, + codaListFolderChildrenTool, + codaListFoldersTool, + codaListFormulasTool, + codaListPageAnalyticsTool, + codaListPagesTool, + codaListPermissionsTool, + codaListRowsTool, + codaListTablesTool, + codaListWorkspaceMembersTool, + codaListWorkspaceRolesTool, + codaPublishDocTool, + codaPushButtonTool, + codaResolveBrowserLinkTool, + codaSearchPrincipalsTool, + codaTriggerAutomationTool, + codaUnpublishDocTool, + codaUpdateAclSettingsTool, + codaUpdateDocTool, + codaUpdateFolderTool, + codaUpdatePageTool, + codaUpdateRowTool, + codaUpsertRowsTool, + codaWhoamiTool, +} + +export * from './types' diff --git a/apps/sim/tools/coda/list_categories.ts b/apps/sim/tools/coda/list_categories.ts new file mode 100644 index 00000000000..21d1cd8e960 --- /dev/null +++ b/apps/sim/tools/coda/list_categories.ts @@ -0,0 +1,48 @@ +import type { CodaAuthParams, CodaListCategoriesResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListCategoriesTool: ToolConfig = { + id: 'coda_list_categories', + name: 'Coda List Doc Categories', + description: 'List the categories that can be applied to a published Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams }, + + request: { + url: () => buildCodaUrl('/categories'), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { items?: Array<{ name?: string }> } + return { + success: true, + output: { + categories: (data.items ?? []) + .map((category) => category.name) + .filter((name): name is string => typeof name === 'string'), + }, + } + }, + + outputs: { + categories: { + type: 'array', + description: 'Category names usable when publishing a doc', + items: { type: 'string', description: 'Category name' }, + }, + }, +} diff --git a/apps/sim/tools/coda/list_columns.ts b/apps/sim/tools/coda/list_columns.ts new file mode 100644 index 00000000000..8ab4f5d4a94 --- /dev/null +++ b/apps/sim/tools/coda/list_columns.ts @@ -0,0 +1,80 @@ +import type { CodaListColumnsParams, CodaListColumnsResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + COLUMN_PROPERTIES, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + mapColumn, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, + type RawCodaColumn, + TABLE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListColumnsTool: ToolConfig = { + id: 'coda_list_columns', + name: 'Coda List Columns', + description: + 'List the columns of a Coda table with their IDs, formats, and formulas. Use column IDs when reading and writing rows.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + tableId: TABLE_ID_PARAM, + visibleOnly: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Only return visible columns (applies to base tables, not views)', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of columns to return (1-100, default 25)', + }, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'tables', [params.tableId, 'tableId'], 'columns'), { + visibleOnly: params.visibleOnly, + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { items?: RawCodaColumn[]; nextPageToken?: string } + return { + success: true, + output: { + columns: (data.items ?? []).map(mapColumn), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + columns: { + type: 'array', + description: 'Columns in the table', + items: { type: 'object', properties: COLUMN_PROPERTIES }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_controls.ts b/apps/sim/tools/coda/list_controls.ts new file mode 100644 index 00000000000..7daa3d63410 --- /dev/null +++ b/apps/sim/tools/coda/list_controls.ts @@ -0,0 +1,73 @@ +import type { CodaListControlsResponse, CodaListDocItemsParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + LIMIT_PARAM, + mapNamedReference, + NAMED_REFERENCE_PROPERTIES, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, + type RawCodaNamedReference, + SORT_BY_NAME_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListControlsTool: ToolConfig = { + id: 'coda_list_controls', + name: 'Coda List Controls', + description: + 'List the controls (sliders, selects, checkboxes, date pickers, buttons, etc.) in a Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + sortBy: SORT_BY_NAME_PARAM, + limit: LIMIT_PARAM, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'controls'), { + sortBy: params.sortBy, + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + items?: RawCodaNamedReference[] + nextPageToken?: string + } + return { + success: true, + output: { + controls: (data.items ?? []).map(mapNamedReference), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + controls: { + type: 'array', + description: 'Controls in the doc', + items: { type: 'object', properties: NAMED_REFERENCE_PROPERTIES }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_custom_domains.ts b/apps/sim/tools/coda/list_custom_domains.ts new file mode 100644 index 00000000000..8ddb09ed44e --- /dev/null +++ b/apps/sim/tools/coda/list_custom_domains.ts @@ -0,0 +1,86 @@ +import type { + CodaCustomDomain, + CodaDocParams, + CodaListCustomDomainsResponse, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + NEXT_PAGE_TOKEN_OUTPUT, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +type RawCustomDomain = Omit & { + lastVerifiedTimestamp?: string +} + +export const codaListCustomDomainsTool: ToolConfig = { + id: 'coda_list_custom_domains', + name: 'Coda List Custom Domains', + description: 'List the custom domains connected to a published Coda doc and their setup status', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'domains')), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + customDocDomains?: RawCustomDomain[] + nextPageToken?: string + } + return { + success: true, + output: { + customDomains: (data.customDocDomains ?? []).map((domain) => ({ + customDocDomain: domain.customDocDomain, + hasCertificate: domain.hasCertificate, + hasDnsDocId: domain.hasDnsDocId, + setupStatus: domain.setupStatus, + domainStatus: domain.domainStatus, + lastVerifiedTimestamp: domain.lastVerifiedTimestamp ?? null, + })), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + customDomains: { + type: 'array', + description: 'Custom domains for the published doc', + items: { + type: 'object', + properties: { + customDocDomain: { type: 'string', description: 'The custom domain' }, + hasCertificate: { type: 'boolean', description: 'Whether the domain has a certificate' }, + hasDnsDocId: { + type: 'boolean', + description: 'Whether the domain DNS points back to this doc', + }, + setupStatus: { type: 'string', description: 'Setup status (pending, succeeded, failed)' }, + domainStatus: { type: 'string', description: 'connected or notConnected' }, + lastVerifiedTimestamp: { + type: 'string', + description: 'When the DNS settings were last checked', + nullable: true, + }, + }, + }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_doc_analytics.ts b/apps/sim/tools/coda/list_doc_analytics.ts new file mode 100644 index 00000000000..12ca60f6dd9 --- /dev/null +++ b/apps/sim/tools/coda/list_doc_analytics.ts @@ -0,0 +1,275 @@ +import type { + CodaDocAnalyticsItem, + CodaListDocAnalyticsParams, + CodaListDocAnalyticsResponse, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + ICON_PROPERTIES, + joinListParam, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +const DOC_METRIC_KEYS = [ + 'views', + 'copies', + 'likes', + 'sessionsMobile', + 'sessionsDesktop', + 'sessionsOther', + 'totalSessions', + 'aiCreditsChat', + 'aiCreditsBlock', + 'aiCreditsColumn', + 'aiCreditsAssistant', + 'aiCreditsReviewer', + 'aiCredits', +] as const + +interface RawDocAnalyticsItem { + doc: { + id: string + title: string + href: string + browserLink: string + icon?: { name?: string; type?: string; browserLink?: string } + createdAt?: string + publishedAt?: string + } + metrics?: Array & { date?: string }> +} + +function toNumberOrNull(value: unknown): number | null { + return typeof value === 'number' ? value : null +} + +export const codaListDocAnalyticsTool: ToolConfig< + CodaListDocAnalyticsParams, + CodaListDocAnalyticsResponse +> = { + id: 'coda_list_doc_analytics', + name: 'Coda List Doc Analytics', + description: + 'Get per-day or cumulative analytics (views, copies, likes, sessions by device, AI credits) for Coda docs', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'Doc IDs to fetch analytics for, as an array or comma-separated list', + }, + workspaceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include docs in this workspace', + }, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term used to filter docs', + }, + isPublished: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Only include published docs', + }, + sinceDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include activity on or after this date (YYYY-MM-DD)', + }, + untilDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include activity on or before this date (YYYY-MM-DD)', + }, + scale: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Aggregation: "daily" (default) or "cumulative"', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Sort field: date, docId, title, createdAt, publishedAt, likes, copies, views, sessionsDesktop, sessionsMobile, sessionsOther, totalSessions, or an aiCredits field', + }, + direction: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort direction: "ascending" or "descending"', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return (1-5000, default 1000)', + }, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl('/analytics/docs', { + docIds: joinListParam(params.docIds, 'docIds'), + workspaceId: optionalTrimmed(params.workspaceId), + query: optionalTrimmed(params.query), + isPublished: params.isPublished, + sinceDate: optionalTrimmed(params.sinceDate), + untilDate: optionalTrimmed(params.untilDate), + scale: params.scale, + orderBy: params.orderBy, + direction: params.direction, + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + items?: RawDocAnalyticsItem[] + nextPageToken?: string + } + const items: CodaDocAnalyticsItem[] = (data.items ?? []).map((item) => ({ + doc: { + id: item.doc.id, + title: item.doc.title, + href: item.doc.href, + browserLink: item.doc.browserLink, + icon: item.doc.icon + ? { + name: item.doc.icon.name ?? null, + type: item.doc.icon.type ?? null, + browserLink: item.doc.icon.browserLink ?? null, + } + : null, + createdAt: item.doc.createdAt ?? null, + publishedAt: item.doc.publishedAt ?? null, + }, + metrics: (item.metrics ?? []).map((metric) => { + const projected: Record = { date: metric.date ?? null } + for (const key of DOC_METRIC_KEYS) projected[key] = toNumberOrNull(metric[key]) + return projected + }), + })) + return { success: true, output: { items, nextPageToken: data.nextPageToken || null } } + }, + + outputs: { + items: { + type: 'array', + description: 'Analytics per doc', + items: { + type: 'object', + properties: { + doc: { + type: 'object', + description: 'Doc the metrics belong to', + properties: { + id: { type: 'string', description: 'Doc ID' }, + title: { type: 'string', description: 'Doc title' }, + href: { type: 'string', description: 'API link to the doc' }, + browserLink: { type: 'string', description: 'Browser link to the doc' }, + icon: { + type: 'object', + description: 'Doc icon', + nullable: true, + properties: ICON_PROPERTIES, + }, + createdAt: { type: 'string', description: 'Doc creation time', nullable: true }, + publishedAt: { type: 'string', description: 'Doc publish time', nullable: true }, + }, + }, + metrics: { + type: 'array', + description: 'Metrics per date', + items: { + type: 'object', + properties: { + date: { + type: 'string', + description: 'Date of the data (YYYY-MM-DD)', + nullable: true, + }, + views: { type: 'number', description: 'Doc views', nullable: true }, + copies: { type: 'number', description: 'Doc copies', nullable: true }, + likes: { type: 'number', description: 'Doc likes', nullable: true }, + sessionsMobile: { + type: 'number', + description: 'Unique mobile visitors', + nullable: true, + }, + sessionsDesktop: { + type: 'number', + description: 'Unique desktop visitors', + nullable: true, + }, + sessionsOther: { + type: 'number', + description: 'Unique visitors on other devices', + nullable: true, + }, + totalSessions: { + type: 'number', + description: 'Sessions across all devices', + nullable: true, + }, + aiCreditsChat: { + type: 'number', + description: 'AI credits used by chat', + nullable: true, + }, + aiCreditsBlock: { + type: 'number', + description: 'AI credits used by AI blocks', + nullable: true, + }, + aiCreditsColumn: { + type: 'number', + description: 'AI credits used by AI columns', + nullable: true, + }, + aiCreditsAssistant: { + type: 'number', + description: 'AI credits used by the assistant', + nullable: true, + }, + aiCreditsReviewer: { + type: 'number', + description: 'AI credits used by the reviewer', + nullable: true, + }, + aiCredits: { type: 'number', description: 'Total AI credits used', nullable: true }, + }, + }, + }, + }, + }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_docs.ts b/apps/sim/tools/coda/list_docs.ts new file mode 100644 index 00000000000..d6c98143381 --- /dev/null +++ b/apps/sim/tools/coda/list_docs.ts @@ -0,0 +1,120 @@ +import type { CodaListDocsParams, CodaListDocsResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + DOC_PROPERTIES, + LIMIT_PARAM, + mapDoc, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, + type RawCodaDoc, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListDocsTool: ToolConfig = { + id: 'coda_list_docs', + name: 'Coda List Docs', + description: + 'List Coda docs the user has opened, most recently used first, filtered by search, owner, publishing, stars, workspace, folder, or source doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search term used to filter docs', + }, + isOwner: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Only return docs owned by the user', + }, + isPublished: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Only return published docs', + }, + isStarred: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'true returns only starred docs; false returns only unstarred docs', + }, + inGallery: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Only return docs visible in the gallery', + }, + sourceDoc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return docs copied from this doc ID', + }, + workspaceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return docs in this workspace (e.g., "ws-1Ab234")', + }, + folderId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return docs in this folder (e.g., "fl-1Ab234")', + }, + limit: LIMIT_PARAM, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl('/docs', { + query: optionalTrimmed(params.query), + isOwner: params.isOwner, + isPublished: params.isPublished, + isStarred: params.isStarred, + inGallery: params.inGallery, + sourceDoc: optionalTrimmed(params.sourceDoc), + workspaceId: optionalTrimmed(params.workspaceId), + folderId: optionalTrimmed(params.folderId), + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { items?: RawCodaDoc[]; nextPageToken?: string } + return { + success: true, + output: { + docs: (data.items ?? []).map(mapDoc), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + docs: { + type: 'array', + description: 'Docs matching the filters', + items: { type: 'object', properties: DOC_PROPERTIES }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_folder_children.ts b/apps/sim/tools/coda/list_folder_children.ts new file mode 100644 index 00000000000..484e67be709 --- /dev/null +++ b/apps/sim/tools/coda/list_folder_children.ts @@ -0,0 +1,86 @@ +import type { + CodaListFolderChildrenParams, + CodaListFolderChildrenResponse, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + codaPath, + FOLDER_CHILD_PROPERTIES, + FOLDER_ID_PARAM, + LIMIT_PARAM, + mapFolder, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, + type RawCodaFolder, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListFolderChildrenTool: ToolConfig< + CodaListFolderChildrenParams, + CodaListFolderChildrenResponse +> = { + id: 'coda_list_folder_children', + name: 'Coda List Subfolders', + description: + 'List the direct subfolders of a Coda folder. Subfolders you cannot access but manage the parent of are returned with only an ID and restricted visibility.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + folderId: FOLDER_ID_PARAM, + limit: LIMIT_PARAM, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaPath('folders', [params.folderId, 'folderId'], 'children'), { + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { items?: RawCodaFolder[]; nextPageToken?: string } + return { + success: true, + output: { + children: (data.items ?? []).map((folder) => { + const { icon: _icon, ...child } = mapFolder(folder) + return { ...child, visibility: folder.visibility ?? 'visible' } + }), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + children: { + type: 'array', + description: 'Direct subfolders', + items: { + type: 'object', + properties: { + ...FOLDER_CHILD_PROPERTIES, + visibility: { + type: 'string', + description: + 'visible, or restricted when only the ID is returned because you cannot access the subfolder', + }, + }, + }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_folders.ts b/apps/sim/tools/coda/list_folders.ts new file mode 100644 index 00000000000..9e42fa5ae17 --- /dev/null +++ b/apps/sim/tools/coda/list_folders.ts @@ -0,0 +1,77 @@ +import type { CodaListFoldersParams, CodaListFoldersResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + FOLDER_PROPERTIES, + LIMIT_PARAM, + mapFolder, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, + type RawCodaFolder, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListFoldersTool: ToolConfig = { + id: 'coda_list_folders', + name: 'Coda List Folders', + description: 'List the Coda folders the user can access, optionally within one workspace', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + workspaceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return folders in this workspace (e.g., "ws-1Ab234")', + }, + isStarred: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'true returns only starred folders; false returns only unstarred folders', + }, + limit: LIMIT_PARAM, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl('/folders', { + workspaceId: optionalTrimmed(params.workspaceId), + isStarred: params.isStarred, + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { items?: RawCodaFolder[]; nextPageToken?: string } + return { + success: true, + output: { + folders: (data.items ?? []).map(mapFolder), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + folders: { + type: 'array', + description: 'Folders the user can access', + items: { type: 'object', properties: FOLDER_PROPERTIES }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_formulas.ts b/apps/sim/tools/coda/list_formulas.ts new file mode 100644 index 00000000000..b63a4c27d86 --- /dev/null +++ b/apps/sim/tools/coda/list_formulas.ts @@ -0,0 +1,72 @@ +import type { CodaListDocItemsParams, CodaListFormulasResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + LIMIT_PARAM, + mapNamedReference, + NAMED_REFERENCE_PROPERTIES, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, + type RawCodaNamedReference, + SORT_BY_NAME_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListFormulasTool: ToolConfig = { + id: 'coda_list_formulas', + name: 'Coda List Formulas', + description: 'List the named formulas in a Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + sortBy: SORT_BY_NAME_PARAM, + limit: LIMIT_PARAM, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'formulas'), { + sortBy: params.sortBy, + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + items?: RawCodaNamedReference[] + nextPageToken?: string + } + return { + success: true, + output: { + formulas: (data.items ?? []).map(mapNamedReference), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + formulas: { + type: 'array', + description: 'Named formulas in the doc', + items: { type: 'object', properties: NAMED_REFERENCE_PROPERTIES }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_page_analytics.ts b/apps/sim/tools/coda/list_page_analytics.ts new file mode 100644 index 00000000000..d517df97f19 --- /dev/null +++ b/apps/sim/tools/coda/list_page_analytics.ts @@ -0,0 +1,179 @@ +import type { + CodaListPageAnalyticsParams, + CodaListPageAnalyticsResponse, + CodaPageAnalyticsItem, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + codaPath, + DOC_ID_PARAM, + ICON_PROPERTIES, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +const PAGE_METRIC_KEYS = [ + 'views', + 'sessions', + 'users', + 'averageSecondsViewed', + 'medianSecondsViewed', + 'tabs', +] as const + +interface RawPageAnalyticsItem { + page: { id: string; name: string; icon?: { name?: string; type?: string; browserLink?: string } } + metrics?: Array & { date?: string }> +} + +export const codaListPageAnalyticsTool: ToolConfig< + CodaListPageAnalyticsParams, + CodaListPageAnalyticsResponse +> = { + id: 'coda_list_page_analytics', + name: 'Coda List Page Analytics', + description: + 'Get daily analytics (views, sessions, users, time viewed) for each page of a Coda doc. Only available for docs in Enterprise workspaces.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + sinceDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include activity on or after this date (YYYY-MM-DD)', + }, + untilDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include activity on or before this date (YYYY-MM-DD)', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return (1-5000, default 1000)', + }, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaPath('analytics', 'docs', [params.docId, 'docId'], 'pages'), { + sinceDate: optionalTrimmed(params.sinceDate), + untilDate: optionalTrimmed(params.untilDate), + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + items?: RawPageAnalyticsItem[] + nextPageToken?: string + } + const items: CodaPageAnalyticsItem[] = (data.items ?? []).map((item) => ({ + page: { + id: item.page.id, + name: item.page.name, + icon: item.page.icon + ? { + name: item.page.icon.name ?? null, + type: item.page.icon.type ?? null, + browserLink: item.page.icon.browserLink ?? null, + } + : null, + }, + metrics: (item.metrics ?? []).map((metric) => { + const projected: Record = { date: metric.date ?? null } + for (const key of PAGE_METRIC_KEYS) { + projected[key] = typeof metric[key] === 'number' ? (metric[key] as number) : null + } + return projected + }), + })) + return { success: true, output: { items, nextPageToken: data.nextPageToken || null } } + }, + + outputs: { + items: { + type: 'array', + description: 'Analytics per page', + items: { + type: 'object', + properties: { + page: { + type: 'object', + description: 'Page the metrics belong to', + properties: { + id: { type: 'string', description: 'Page ID' }, + name: { type: 'string', description: 'Page name' }, + icon: { + type: 'object', + description: 'Page icon', + nullable: true, + properties: ICON_PROPERTIES, + }, + }, + }, + metrics: { + type: 'array', + description: 'Metrics per date', + items: { + type: 'object', + properties: { + date: { + type: 'string', + description: 'Date of the data (YYYY-MM-DD)', + nullable: true, + }, + views: { type: 'number', description: 'Page views that day', nullable: true }, + sessions: { + type: 'number', + description: 'Unique browsers that viewed the page', + nullable: true, + }, + users: { + type: 'number', + description: 'Unique Coda users that viewed the page', + nullable: true, + }, + averageSecondsViewed: { + type: 'number', + description: 'Average seconds the page was viewed', + nullable: true, + }, + medianSecondsViewed: { + type: 'number', + description: 'Median seconds the page was viewed', + nullable: true, + }, + tabs: { + type: 'number', + description: 'Unique tabs that opened the doc', + nullable: true, + }, + }, + }, + }, + }, + }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_pages.ts b/apps/sim/tools/coda/list_pages.ts new file mode 100644 index 00000000000..0e439b93645 --- /dev/null +++ b/apps/sim/tools/coda/list_pages.ts @@ -0,0 +1,66 @@ +import type { CodaListPagesParams, CodaListPagesResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + LIMIT_PARAM, + mapPage, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_PROPERTIES, + PAGE_TOKEN_PARAM, + type RawCodaPage, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListPagesTool: ToolConfig = { + id: 'coda_list_pages', + name: 'Coda List Pages', + description: 'List the pages in a Coda doc, including their hierarchy', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + limit: LIMIT_PARAM, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'pages'), { + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { items?: RawCodaPage[]; nextPageToken?: string } + return { + success: true, + output: { + pages: (data.items ?? []).map(mapPage), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + pages: { + type: 'array', + description: 'Pages in the doc', + items: { type: 'object', properties: PAGE_PROPERTIES }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_permissions.ts b/apps/sim/tools/coda/list_permissions.ts new file mode 100644 index 00000000000..8329573c7b3 --- /dev/null +++ b/apps/sim/tools/coda/list_permissions.ts @@ -0,0 +1,72 @@ +import type { CodaListPermissionsParams, CodaListPermissionsResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + LIMIT_PARAM, + mapPermission, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, + PERMISSION_PROPERTIES, + type RawCodaPermission, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListPermissionsTool: ToolConfig< + CodaListPermissionsParams, + CodaListPermissionsResponse +> = { + id: 'coda_list_permissions', + name: 'Coda List Permissions', + description: 'List who a Coda doc is shared with and their access levels', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + limit: LIMIT_PARAM, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'acl', 'permissions'), { + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + items?: RawCodaPermission[] + nextPageToken?: string + } + return { + success: true, + output: { + permissions: (data.items ?? []).map(mapPermission), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + permissions: { + type: 'array', + description: 'Permissions granted on the doc', + items: { type: 'object', properties: PERMISSION_PROPERTIES }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_rows.ts b/apps/sim/tools/coda/list_rows.ts new file mode 100644 index 00000000000..4d6f978b494 --- /dev/null +++ b/apps/sim/tools/coda/list_rows.ts @@ -0,0 +1,123 @@ +import type { CodaListRowsParams, CodaListRowsResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + LIMIT_PARAM, + mapRow, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, + type RawCodaRow, + ROW_PROPERTIES, + TABLE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListRowsTool: ToolConfig = { + id: 'coda_list_rows', + name: 'Coda List Rows', + description: + 'List rows in a Coda table or view, optionally filtered by a column value, sorted, or limited to rows changed since a sync token', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + tableId: TABLE_ID_PARAM, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter as :. Quote column names and string values, e.g., c-tuVwxYz:"Apple" or "Status":"Done"', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Sort order: "createdAt" (default), "updatedAt", or "natural" (view order; implies visibleOnly)', + }, + useColumnNames: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Key cell values by column name instead of column ID', + }, + valueFormat: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cell value format: "simple" (default), "simpleWithArrays", or "rich"', + }, + visibleOnly: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Only return visible rows and columns', + }, + syncToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'nextSyncToken from a previous call, to return only rows changed since then', + }, + limit: LIMIT_PARAM, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'tables', [params.tableId, 'tableId'], 'rows'), { + query: optionalTrimmed(params.query), + sortBy: params.sortBy, + useColumnNames: params.useColumnNames, + valueFormat: params.valueFormat, + visibleOnly: params.visibleOnly, + syncToken: optionalTrimmed(params.syncToken), + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + items?: RawCodaRow[] + nextPageToken?: string + nextSyncToken?: string + } + return { + success: true, + output: { + rows: (data.items ?? []).map(mapRow), + nextPageToken: data.nextPageToken || null, + nextSyncToken: data.nextSyncToken ?? null, + }, + } + }, + + outputs: { + rows: { + type: 'array', + description: 'Rows in the table', + items: { type: 'object', properties: ROW_PROPERTIES }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + nextSyncToken: { + type: 'string', + description: 'Token to pass as syncToken later to fetch only rows changed after this call', + nullable: true, + }, + }, +} diff --git a/apps/sim/tools/coda/list_tables.ts b/apps/sim/tools/coda/list_tables.ts new file mode 100644 index 00000000000..6597124bd97 --- /dev/null +++ b/apps/sim/tools/coda/list_tables.ts @@ -0,0 +1,81 @@ +import type { CodaListTablesParams, CodaListTablesResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + joinListParam, + LIMIT_PARAM, + mapTableReference, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, + type RawCodaTableReference, + SORT_BY_NAME_PARAM, + TABLE_REFERENCE_PROPERTIES, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListTablesTool: ToolConfig = { + id: 'coda_list_tables', + name: 'Coda List Tables', + description: 'List the tables and views in a Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + tableTypes: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Table types to include, as an array or comma-separated list of "table", "view", "database" (defaults to all)', + }, + sortBy: SORT_BY_NAME_PARAM, + limit: LIMIT_PARAM, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'tables'), { + tableTypes: joinListParam(params.tableTypes, 'tableTypes'), + sortBy: params.sortBy, + limit: params.limit, + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + items?: RawCodaTableReference[] + nextPageToken?: string + } + return { + success: true, + output: { + tables: (data.items ?? []).map(mapTableReference), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + tables: { + type: 'array', + description: 'Tables and views in the doc', + items: { type: 'object', properties: TABLE_REFERENCE_PROPERTIES }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_workspace_members.ts b/apps/sim/tools/coda/list_workspace_members.ts new file mode 100644 index 00000000000..8b7d8eb9468 --- /dev/null +++ b/apps/sim/tools/coda/list_workspace_members.ts @@ -0,0 +1,150 @@ +import type { + CodaListWorkspaceMembersParams, + CodaListWorkspaceMembersResponse, + CodaWorkspaceMember, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + codaPath, + joinListParam, + NEXT_PAGE_TOKEN_OUTPUT, + optionalTrimmed, + PAGE_TOKEN_PARAM, + WORKSPACE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +type RawWorkspaceMember = Partial & { + email: string + name: string + role: string + registeredAt: string +} + +export const codaListWorkspaceMembersTool: ToolConfig< + CodaListWorkspaceMembersParams, + CodaListWorkspaceMembersResponse +> = { + id: 'coda_list_workspace_members', + name: 'Coda List Workspace Members', + description: + 'List the members of a Coda workspace with their roles and doc activity, requesting user first. The workspace must belong to an organization.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + workspaceId: WORKSPACE_ID_PARAM, + includedRoles: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Only return members with these roles, as an array or comma-separated list of "Admin", "DocMaker", "Editor"', + }, + pageToken: PAGE_TOKEN_PARAM, + }, + + request: { + url: (params) => + buildCodaUrl(codaPath('workspaces', [params.workspaceId, 'workspaceId'], 'users'), { + includedRoles: joinListParam(params.includedRoles, 'includedRoles'), + pageToken: optionalTrimmed(params.pageToken), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + items?: RawWorkspaceMember[] + nextPageToken?: string + } + return { + success: true, + output: { + members: (data.items ?? []).map((member) => ({ + email: member.email, + name: member.name, + role: member.role, + pictureUrl: member.pictureUrl ?? null, + registeredAt: member.registeredAt, + roleChangedAt: member.roleChangedAt ?? null, + lastActiveAt: member.lastActiveAt ?? null, + ownedDocs: member.ownedDocs ?? null, + docsLastActiveAt: member.docsLastActiveAt ?? null, + docCollaboratorCount: member.docCollaboratorCount ?? null, + totalDocs: member.totalDocs ?? null, + totalDocsLastActiveAt: member.totalDocsLastActiveAt ?? null, + totalDocCollaboratorsLast90Days: member.totalDocCollaboratorsLast90Days ?? null, + })), + nextPageToken: data.nextPageToken || null, + }, + } + }, + + outputs: { + members: { + type: 'array', + description: 'Workspace members', + items: { + type: 'object', + properties: { + email: { type: 'string', description: 'Email address' }, + name: { type: 'string', description: 'Name' }, + role: { type: 'string', description: 'Workspace role (Admin, DocMaker, Editor)' }, + pictureUrl: { type: 'string', description: 'Avatar link', nullable: true }, + registeredAt: { type: 'string', description: 'When the user joined the workspace' }, + roleChangedAt: { + type: 'string', + description: 'When the role last changed', + nullable: true, + }, + lastActiveAt: { + type: 'string', + description: 'Date the user last acted in any workspace', + nullable: true, + }, + ownedDocs: { + type: 'number', + description: 'Docs the user owns in this workspace', + nullable: true, + }, + docsLastActiveAt: { + type: 'string', + description: 'Date anyone last accessed a doc the user owns', + nullable: true, + }, + docCollaboratorCount: { + type: 'number', + description: 'Collaborators on docs the user owns in the last 90 days', + nullable: true, + }, + totalDocs: { + type: 'number', + description: 'Docs the user owns, manages, or added pages to in the last 90 days', + nullable: true, + }, + totalDocsLastActiveAt: { + type: 'string', + description: 'Date anyone last accessed a doc the user owns or contributed to', + nullable: true, + }, + totalDocCollaboratorsLast90Days: { + type: 'number', + description: 'Unique viewers of docs the user owns, manages, or added pages to', + nullable: true, + }, + }, + }, + }, + nextPageToken: NEXT_PAGE_TOKEN_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/list_workspace_roles.ts b/apps/sim/tools/coda/list_workspace_roles.ts new file mode 100644 index 00000000000..e0e3a5ec204 --- /dev/null +++ b/apps/sim/tools/coda/list_workspace_roles.ts @@ -0,0 +1,76 @@ +import type { + CodaListWorkspaceRolesResponse, + CodaWorkspaceParams, + CodaWorkspaceRoleActivity, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + codaPath, + WORKSPACE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaListWorkspaceRolesTool: ToolConfig< + CodaWorkspaceParams, + CodaListWorkspaceRolesResponse +> = { + id: 'coda_list_workspace_roles', + name: 'Coda List Workspace Role Activity', + description: + 'Get monthly counts of active and inactive Admins, Doc Makers, and Editors in a workspace. The workspace must belong to an organization.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, workspaceId: WORKSPACE_ID_PARAM }, + + request: { + url: (params) => + buildCodaUrl(codaPath('workspaces', [params.workspaceId, 'workspaceId'], 'roles')), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { items?: CodaWorkspaceRoleActivity[] } + return { + success: true, + output: { + roleActivity: (data.items ?? []).map((item) => ({ + month: item.month, + activeAdminCount: item.activeAdminCount, + activeDocMakerCount: item.activeDocMakerCount, + activeEditorCount: item.activeEditorCount, + inactiveAdminCount: item.inactiveAdminCount, + inactiveDocMakerCount: item.inactiveDocMakerCount, + inactiveEditorCount: item.inactiveEditorCount, + })), + }, + } + }, + + outputs: { + roleActivity: { + type: 'array', + description: 'Role counts per month', + items: { + type: 'object', + properties: { + month: { type: 'string', description: 'Month of the data (YYYY-MM-DD)' }, + activeAdminCount: { type: 'number', description: 'Active Admins' }, + activeDocMakerCount: { type: 'number', description: 'Active Doc Makers' }, + activeEditorCount: { type: 'number', description: 'Active Editors' }, + inactiveAdminCount: { type: 'number', description: 'Inactive Admins' }, + inactiveDocMakerCount: { type: 'number', description: 'Inactive Doc Makers' }, + inactiveEditorCount: { type: 'number', description: 'Inactive Editors' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/coda/publish_doc.ts b/apps/sim/tools/coda/publish_doc.ts new file mode 100644 index 00000000000..07ac562099a --- /dev/null +++ b/apps/sim/tools/coda/publish_doc.ts @@ -0,0 +1,80 @@ +import type { CodaPublishDocParams, CodaRequestIdResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + optionalTrimmed, + parseStringList, + REQUEST_ID_OUTPUT, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaPublishDocTool: ToolConfig = { + id: 'coda_publish_doc', + name: 'Coda Publish Doc', + description: + 'Publish a Coda doc or update its publishing settings: URL slug, discoverability, categories, and interaction mode. The doc owner needs a Coda maker profile.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + slug: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL slug for the published doc (e.g., "my-doc")', + }, + discoverable: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the published doc is discoverable in the gallery', + }, + categoryNames: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Category names to apply, as an array or comma-separated list (see List Doc Categories)', + }, + mode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Interaction mode for viewers: "view", "play", or "edit"', + }, + }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'publish')), + method: 'PUT', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const categoryNames = parseStringList(params.categoryNames, 'categoryNames') + return { + ...(optionalTrimmed(params.slug) ? { slug: optionalTrimmed(params.slug) } : {}), + ...(typeof params.discoverable === 'boolean' ? { discoverable: params.discoverable } : {}), + ...(categoryNames.length > 0 ? { categoryNames } : {}), + ...(params.mode ? { mode: params.mode } : {}), + } + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string } + return { success: true, output: { requestId: data.requestId } } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/push_button.ts b/apps/sim/tools/coda/push_button.ts new file mode 100644 index 00000000000..5f1c69a1489 --- /dev/null +++ b/apps/sim/tools/coda/push_button.ts @@ -0,0 +1,70 @@ +import type { CodaPushButtonParams, CodaPushButtonResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + REQUEST_ID_OUTPUT, + ROW_ID_PARAM, + TABLE_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaPushButtonTool: ToolConfig = { + id: 'coda_push_button', + name: 'Coda Push Button', + description: + 'Push a button column on a row of a Coda table, running its action. The button can perform any action in the doc.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + tableId: TABLE_ID_PARAM, + rowId: ROW_ID_PARAM, + columnId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID or name of the button column (e.g., "c-tuVwxYz")', + }, + }, + + request: { + url: (params) => + buildCodaUrl( + codaDocPath( + params.docId, + 'tables', + [params.tableId, 'tableId'], + 'rows', + [params.rowId, 'rowId'], + 'buttons', + [params.columnId, 'columnId'] + ) + ), + method: 'POST', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string; rowId: string; columnId: string } + return { + success: true, + output: { requestId: data.requestId, rowId: data.rowId, columnId: data.columnId }, + } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + rowId: { type: 'string', description: 'ID of the row containing the button' }, + columnId: { type: 'string', description: 'ID of the button column' }, + }, +} diff --git a/apps/sim/tools/coda/resolve_browser_link.ts b/apps/sim/tools/coda/resolve_browser_link.ts new file mode 100644 index 00000000000..2622ec6f155 --- /dev/null +++ b/apps/sim/tools/coda/resolve_browser_link.ts @@ -0,0 +1,95 @@ +import type { + CodaResolveBrowserLinkParams, + CodaResolveBrowserLinkResponse, +} from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + requiredTrimmed, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaResolveBrowserLinkTool: ToolConfig< + CodaResolveBrowserLinkParams, + CodaResolveBrowserLinkResponse +> = { + id: 'coda_resolve_browser_link', + name: 'Coda Resolve Browser Link', + description: + 'Resolve a Coda browser URL (doc, page, table, row, etc.) into its resource type and ID for use in other Coda operations', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + url: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Coda browser link, e.g., https://coda.io/d/_dAbCDeFGH/Launch-Status_sumnO', + }, + degradeGracefully: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: + 'If the linked object was deleted, resolve the nearest existing parent (up to the doc) instead of failing', + }, + }, + + request: { + url: (params) => + buildCodaUrl('/resolveBrowserLink', { + url: requiredTrimmed(params.url, 'url'), + degradeGracefully: params.degradeGracefully, + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { + browserLink?: string + resource: { type: string; id: string; name?: string; href: string } + } + return { + success: true, + output: { + browserLink: data.browserLink ?? null, + resource: { + type: data.resource.type, + id: data.resource.id, + name: data.resource.name ?? null, + href: data.resource.href, + }, + }, + } + }, + + outputs: { + browserLink: { + type: 'string', + description: 'Canonical browser link to the resource', + nullable: true, + }, + resource: { + type: 'object', + description: 'The resolved resource', + properties: { + type: { + type: 'string', + description: 'Resource type (doc, page, table, row, column, formula, control, etc.)', + }, + id: { type: 'string', description: 'Resource ID' }, + name: { type: 'string', description: 'Resource name', nullable: true }, + href: { type: 'string', description: 'API link to the resource' }, + }, + }, + }, +} diff --git a/apps/sim/tools/coda/search_principals.ts b/apps/sim/tools/coda/search_principals.ts new file mode 100644 index 00000000000..ec1153eb59e --- /dev/null +++ b/apps/sim/tools/coda/search_principals.ts @@ -0,0 +1,96 @@ +import type { CodaSearchPrincipalsParams, CodaSearchPrincipalsResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + optionalTrimmed, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +interface RawSearchPrincipals { + users?: Array<{ name: string; loginId: string; pictureLink?: string }> + groups?: Array<{ groupId: string; groupName: string }> +} + +export const codaSearchPrincipalsTool: ToolConfig< + CodaSearchPrincipalsParams, + CodaSearchPrincipalsResponse +> = { + id: 'coda_search_principals', + name: 'Coda Search Principals', + description: + 'Search for users and groups a Coda doc can be shared with (up to 20 of each). Returns nothing without a query.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + query: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name or email to search for', + }, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'acl', 'principals', 'search'), { + query: optionalTrimmed(params.query), + }), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawSearchPrincipals + return { + success: true, + output: { + users: (data.users ?? []).map((user) => ({ + name: user.name, + loginId: user.loginId, + pictureLink: user.pictureLink ?? null, + })), + groups: (data.groups ?? []).map((group) => ({ + groupId: group.groupId, + groupName: group.groupName, + })), + }, + } + }, + + outputs: { + users: { + type: 'array', + description: 'Matching users', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'User name' }, + loginId: { type: 'string', description: 'User email address' }, + pictureLink: { type: 'string', description: 'Avatar link', nullable: true }, + }, + }, + }, + groups: { + type: 'array', + description: 'Matching groups', + items: { + type: 'object', + properties: { + groupId: { type: 'string', description: 'Group ID' }, + groupName: { type: 'string', description: 'Group name' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/coda/trigger_automation.ts b/apps/sim/tools/coda/trigger_automation.ts new file mode 100644 index 00000000000..664d0713d66 --- /dev/null +++ b/apps/sim/tools/coda/trigger_automation.ts @@ -0,0 +1,69 @@ +import type { CodaRequestIdResponse, CodaTriggerAutomationParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + parseJsonInput, + REQUEST_ID_OUTPUT, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaTriggerAutomationTool: ToolConfig< + CodaTriggerAutomationParams, + CodaRequestIdResponse +> = { + id: 'coda_trigger_automation', + name: 'Coda Trigger Automation', + description: + 'Trigger a webhook-invoked automation in a Coda doc, optionally passing a JSON payload the automation can read', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + ruleId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the automation rule (e.g., "grid-auto-b3Jmey6jBS")', + }, + payload: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'JSON object passed to the automation', + }, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'hooks', 'automation', [params.ruleId, 'ruleId'])), + method: 'POST', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const payload = parseJsonInput(params.payload, 'payload') + if (payload === undefined || payload === null) return {} + if (typeof payload !== 'object' || Array.isArray(payload)) { + throw new Error('payload must be a JSON object') + } + return payload as Record + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string } + return { success: true, output: { requestId: data.requestId } } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + }, +} diff --git a/apps/sim/tools/coda/types.ts b/apps/sim/tools/coda/types.ts new file mode 100644 index 00000000000..b32167c0b5a --- /dev/null +++ b/apps/sim/tools/coda/types.ts @@ -0,0 +1,780 @@ +import type { ToolResponse } from '@/tools/types' + +export interface CodaAuthParams { + accessToken: string +} + +export interface CodaPaginationParams { + limit?: number + pageToken?: string +} + +export interface CodaDocParams extends CodaAuthParams { + docId: string +} + +export interface CodaPageParams extends CodaDocParams { + pageId: string +} + +export interface CodaTableParams extends CodaDocParams { + tableId: string +} + +export interface CodaRowParams extends CodaTableParams { + rowId: string +} + +export interface CodaIcon { + name: string | null + type: string | null + browserLink: string | null +} + +export interface CodaPerson { + name: string | null + email: string | null +} + +export interface CodaPageRef { + id: string + name: string | null + href: string | null + browserLink: string | null +} + +export interface CodaTableRef { + id: string + name: string | null + tableType: string | null + href: string | null + browserLink: string | null +} + +export interface CodaWorkspaceRef { + id: string + name: string | null + organizationId: string | null + browserLink: string | null +} + +export interface CodaDoc { + id: string + name: string + href: string + browserLink: string + icon: CodaIcon | null + owner: string | null + ownerName: string | null + createdAt: string | null + updatedAt: string | null + workspace: CodaWorkspaceRef | null + folder: { id: string; name: string | null; browserLink: string | null } | null + sourceDoc: { id: string; href: string | null; browserLink: string | null } | null + docSize: { + totalRowCount: number | null + tableAndViewCount: number | null + baseTableCount: number | null + pageCount: number | null + overApiSizeLimit: boolean | null + } | null + published: { + description: string | null + browserLink: string | null + imageLink: string | null + discoverable: boolean | null + earnCredit: boolean | null + mode: string | null + categories: string[] + } | null +} + +export interface CodaPage { + id: string + name: string + subtitle: string | null + href: string + browserLink: string + contentType: string | null + isHidden: boolean | null + isEffectivelyHidden: boolean | null + icon: CodaIcon | null + image: { + browserLink: string | null + type: string | null + width: number | null + height: number | null + } | null + parent: CodaPageRef | null + children: CodaPageRef[] + authors: CodaPerson[] + createdAt: string | null + createdBy: CodaPerson | null + updatedAt: string | null + updatedBy: CodaPerson | null +} + +export interface CodaTableReference { + id: string + name: string + tableType: string | null + href: string + browserLink: string + parent: CodaPageRef | null +} + +export interface CodaTable extends CodaTableReference { + parentTable: CodaTableRef | null + displayColumnId: string | null + rowCount: number | null + sorts: Array<{ columnId: string | null; direction: string | null }> + layout: string | null + filter: { + valid: boolean | null + isVolatile: boolean | null + hasUserFormula: boolean | null + hasTodayFormula: boolean | null + hasNowFormula: boolean | null + } | null + createdAt: string | null + updatedAt: string | null +} + +export interface CodaColumn { + id: string + name: string + href: string + display: boolean | null + calculated: boolean | null + formula: string | null + defaultValue: string | null + format: Record | null + parentTable: CodaTableRef | null +} + +export interface CodaRow { + id: string + name: string + index: number | null + href: string + browserLink: string + createdAt: string | null + updatedAt: string | null + values: Record + parentTable: CodaTableRef | null +} + +export interface CodaNamedReference { + id: string + name: string + href: string + parent: CodaPageRef | null +} + +export interface CodaFormula extends CodaNamedReference { + value: unknown +} + +export interface CodaControl extends CodaNamedReference { + controlType: string | null + value: unknown +} + +export interface CodaFolder { + id: string + name: string | null + browserLink: string | null + description: string | null + icon: CodaIcon | null + iconColor: string | null + createdAt: string | null + canEdit: boolean | null + workspace: CodaWorkspaceRef | null +} + +export interface CodaPermission { + id: string + access: string + principal: { + type: string | null + email: string | null + groupId: string | null + groupName: string | null + domain: string | null + workspaceId: string | null + internalAccessType: string | null + } +} + +export type CodaListResponse = ToolResponse & { + output: Record & { nextPageToken: string | null } +} + +export interface CodaRequestIdResponse extends ToolResponse { + output: { requestId: string } +} + +export interface CodaWhoamiResponse extends ToolResponse { + output: { + name: string + loginId: string + pictureLink: string | null + scoped: boolean | null + tokenName: string | null + workspace: CodaWorkspaceRef | null + } +} + +export interface CodaListDocsParams extends CodaAuthParams, CodaPaginationParams { + query?: string + isOwner?: boolean + isPublished?: boolean + isStarred?: boolean + inGallery?: boolean + sourceDoc?: string + workspaceId?: string + folderId?: string +} + +export type CodaListDocsResponse = CodaListResponse<'docs', CodaDoc> + +export interface CodaDocResponse extends ToolResponse { + output: { doc: CodaDoc } +} + +export interface CodaPageContentParams { + pageType?: string + contentFormat?: string + content?: string + embedUrl?: string + renderMethod?: string + sourceDocId?: string + sourcePageId?: string + syncMode?: string + includeSubpages?: boolean +} + +export interface CodaCreateDocParams extends CodaAuthParams, CodaPageContentParams { + title?: string + sourceDoc?: string + timezone?: string + folderId?: string + pageName?: string + pageSubtitle?: string + iconName?: string + imageUrl?: string +} + +export interface CodaCreateDocResponse extends ToolResponse { + output: { doc: CodaDoc; requestId: string | null } +} + +export interface CodaUpdateDocParams extends CodaDocParams { + title?: string + iconName?: string +} + +export interface CodaDocIdResponse extends ToolResponse { + output: { docId: string } +} + +export interface CodaListCategoriesResponse extends ToolResponse { + output: { categories: string[] } +} + +export interface CodaPublishDocParams extends CodaDocParams { + slug?: string + discoverable?: boolean + categoryNames?: unknown + mode?: string +} + +export interface CodaSharingMetadataResponse extends ToolResponse { + output: { + canShare: boolean + canShareWithWorkspace: boolean + canShareWithOrg: boolean + canCopy: boolean + } +} + +export interface CodaAclSettingsParams extends CodaDocParams { + allowEditorsToChangePermissions?: boolean + allowCopying?: boolean + allowViewersToRequestEditing?: boolean +} + +export interface CodaAclSettingsResponse extends ToolResponse { + output: { + allowEditorsToChangePermissions: boolean + allowCopying: boolean + allowViewersToRequestEditing: boolean + } +} + +export interface CodaSearchPrincipalsParams extends CodaDocParams { + query?: string +} + +export interface CodaSearchPrincipalsResponse extends ToolResponse { + output: { + users: Array<{ name: string; loginId: string; pictureLink: string | null }> + groups: Array<{ groupId: string; groupName: string }> + } +} + +export interface CodaListPermissionsParams extends CodaDocParams, CodaPaginationParams {} + +export type CodaListPermissionsResponse = CodaListResponse<'permissions', CodaPermission> + +export type CodaPrincipalType = 'email' | 'group' | 'domain' | 'workspace' | 'anyone' + +export interface CodaAddPermissionParams extends CodaDocParams { + access: 'readonly' | 'write' | 'comment' + principalType: CodaPrincipalType + principal?: string + suppressEmail?: boolean +} + +export interface CodaAddPermissionResponse extends ToolResponse { + output: { docId: string; access: string; principalType: string } +} + +export interface CodaDeletePermissionParams extends CodaDocParams { + permissionId: string +} + +export interface CodaDeletePermissionResponse extends ToolResponse { + output: { docId: string; permissionId: string } +} + +export interface CodaListPagesParams extends CodaDocParams, CodaPaginationParams {} + +export type CodaListPagesResponse = CodaListResponse<'pages', CodaPage> + +export interface CodaPageResponse extends ToolResponse { + output: { page: CodaPage } +} + +export interface CodaCreatePageParams extends CodaDocParams, CodaPageContentParams { + name?: string + subtitle?: string + iconName?: string + imageUrl?: string + parentPageId?: string +} + +export interface CodaUpdatePageParams extends CodaPageParams { + name?: string + subtitle?: string + iconName?: string + imageUrl?: string + isHidden?: boolean + insertionMode?: string + elementId?: string + contentFormat?: string + content?: string +} + +export interface CodaPageMutationResponse extends ToolResponse { + output: { requestId: string; pageId: string } +} + +export interface CodaDeletePageContentParams extends CodaPageParams { + elementIds?: unknown + deleteAll?: boolean +} + +export interface CodaGetPageContentParams extends CodaPageParams, CodaPaginationParams {} + +export interface CodaPageContentItem { + id: string + type: string + style: string | null + format: string | null + content: string | null + lineLevel: number | null +} + +export type CodaGetPageContentResponse = CodaListResponse<'items', CodaPageContentItem> + +export interface CodaExportPageParams extends CodaPageParams { + outputFormat: string +} + +export interface CodaExportPageResponse extends ToolResponse { + output: { exportId: string; status: string; href: string } +} + +export interface CodaGetPageExportStatusParams extends CodaPageParams { + exportId: string +} + +export interface CodaPageExportStatusResponse extends ToolResponse { + output: { + exportId: string + status: string + href: string + downloadLink: string | null + exportError: string | null + } +} + +export interface CodaListTablesParams extends CodaDocParams, CodaPaginationParams { + sortBy?: string + tableTypes?: unknown +} + +export type CodaListTablesResponse = CodaListResponse<'tables', CodaTableReference> + +export interface CodaGetTableParams extends CodaTableParams { + useUpdatedTableLayouts?: boolean +} + +export interface CodaTableResponse extends ToolResponse { + output: { table: CodaTable } +} + +export interface CodaListColumnsParams extends CodaTableParams, CodaPaginationParams { + visibleOnly?: boolean +} + +export type CodaListColumnsResponse = CodaListResponse<'columns', CodaColumn> + +export interface CodaGetColumnParams extends CodaTableParams { + columnId: string +} + +export interface CodaColumnResponse extends ToolResponse { + output: { column: CodaColumn } +} + +export interface CodaListRowsParams extends CodaTableParams, CodaPaginationParams { + query?: string + sortBy?: string + useColumnNames?: boolean + valueFormat?: string + visibleOnly?: boolean + syncToken?: string +} + +export interface CodaListRowsResponse extends ToolResponse { + output: { rows: CodaRow[]; nextPageToken: string | null; nextSyncToken: string | null } +} + +export interface CodaGetRowParams extends CodaRowParams { + useColumnNames?: boolean + valueFormat?: string +} + +export interface CodaRowResponse extends ToolResponse { + output: { row: CodaRow } +} + +export interface CodaUpsertRowsParams extends CodaTableParams { + rows: unknown + keyColumns?: unknown + disableParsing?: boolean +} + +export interface CodaUpsertRowsResponse extends ToolResponse { + output: { requestId: string; addedRowIds: string[] } +} + +export interface CodaUpdateRowParams extends CodaRowParams { + cells: unknown + disableParsing?: boolean +} + +export interface CodaRowMutationResponse extends ToolResponse { + output: { requestId: string; rowId: string } +} + +export interface CodaDeleteRowsParams extends CodaTableParams { + rowIds: unknown +} + +export interface CodaDeleteRowsResponse extends ToolResponse { + output: { requestId: string; rowIds: string[] } +} + +export interface CodaPushButtonParams extends CodaRowParams { + columnId: string +} + +export interface CodaPushButtonResponse extends ToolResponse { + output: { requestId: string; rowId: string; columnId: string } +} + +export interface CodaListDocItemsParams extends CodaDocParams, CodaPaginationParams { + sortBy?: string +} + +export type CodaListFormulasResponse = CodaListResponse<'formulas', CodaNamedReference> + +export interface CodaGetFormulaParams extends CodaDocParams { + formulaId: string +} + +export interface CodaFormulaResponse extends ToolResponse { + output: { formula: CodaFormula } +} + +export type CodaListControlsResponse = CodaListResponse<'controls', CodaNamedReference> + +export interface CodaGetControlParams extends CodaDocParams { + controlId: string +} + +export interface CodaControlResponse extends ToolResponse { + output: { control: CodaControl } +} + +export interface CodaListFoldersParams extends CodaAuthParams, CodaPaginationParams { + workspaceId?: string + isStarred?: boolean +} + +export type CodaListFoldersResponse = CodaListResponse<'folders', CodaFolder> + +export interface CodaFolderParams extends CodaAuthParams { + folderId: string +} + +export interface CodaFolderResponse extends ToolResponse { + output: { folder: CodaFolder } +} + +export interface CodaCreateFolderParams extends CodaAuthParams { + name: string + workspaceId: string + description?: string +} + +export interface CodaUpdateFolderParams extends CodaFolderParams { + name?: string + description?: string +} + +export interface CodaDeleteFolderResponse extends ToolResponse { + output: { folderId: string } +} + +export interface CodaListFolderChildrenParams extends CodaFolderParams, CodaPaginationParams {} + +export type CodaFolderChild = Omit & { visibility: string } + +export type CodaListFolderChildrenResponse = CodaListResponse<'children', CodaFolderChild> + +export interface CodaWorkspaceParams extends CodaAuthParams { + workspaceId: string +} + +export interface CodaListWorkspaceMembersParams extends CodaWorkspaceParams { + includedRoles?: unknown + pageToken?: string +} + +export interface CodaWorkspaceMember { + email: string + name: string + role: string + pictureUrl: string | null + registeredAt: string + roleChangedAt: string | null + lastActiveAt: string | null + ownedDocs: number | null + docsLastActiveAt: string | null + docCollaboratorCount: number | null + totalDocs: number | null + totalDocsLastActiveAt: string | null + totalDocCollaboratorsLast90Days: number | null +} + +export type CodaListWorkspaceMembersResponse = CodaListResponse<'members', CodaWorkspaceMember> + +export interface CodaChangeUserRoleParams extends CodaWorkspaceParams { + email: string + newRole: string +} + +export interface CodaChangeUserRoleResponse extends ToolResponse { + output: { email: string; newRole: string; roleChangedAt: string } +} + +export interface CodaWorkspaceRoleActivity { + month: string + activeAdminCount: number + activeDocMakerCount: number + activeEditorCount: number + inactiveAdminCount: number + inactiveDocMakerCount: number + inactiveEditorCount: number +} + +export interface CodaListWorkspaceRolesResponse extends ToolResponse { + output: { roleActivity: CodaWorkspaceRoleActivity[] } +} + +export interface CodaListDocAnalyticsParams extends CodaAuthParams, CodaPaginationParams { + docIds?: unknown + workspaceId?: string + query?: string + isPublished?: boolean + sinceDate?: string + untilDate?: string + scale?: string + orderBy?: string + direction?: string +} + +export interface CodaDocAnalyticsItem { + doc: { + id: string + title: string + href: string + browserLink: string + icon: CodaIcon | null + createdAt: string | null + publishedAt: string | null + } + metrics: Array> +} + +export type CodaListDocAnalyticsResponse = CodaListResponse<'items', CodaDocAnalyticsItem> + +export interface CodaListPageAnalyticsParams extends CodaDocParams, CodaPaginationParams { + sinceDate?: string + untilDate?: string +} + +export interface CodaPageAnalyticsItem { + page: { id: string; name: string; icon: CodaIcon | null } + metrics: Array> +} + +export type CodaListPageAnalyticsResponse = CodaListResponse<'items', CodaPageAnalyticsItem> + +export interface CodaDocAnalyticsSummaryParams extends CodaAuthParams { + isPublished?: boolean + sinceDate?: string + untilDate?: string + workspaceId?: string +} + +export interface CodaDocAnalyticsSummaryResponse extends ToolResponse { + output: { totalSessions: number } +} + +export interface CodaAnalyticsLastUpdatedResponse extends ToolResponse { + output: { + docAnalyticsLastUpdated: string + packAnalyticsLastUpdated: string + packFormulaAnalyticsLastUpdated: string + } +} + +export interface CodaCustomDomain { + customDocDomain: string + hasCertificate: boolean + hasDnsDocId: boolean + setupStatus: string + domainStatus: string + lastVerifiedTimestamp: string | null +} + +export interface CodaListCustomDomainsResponse extends ToolResponse { + output: { customDomains: CodaCustomDomain[]; nextPageToken: string | null } +} + +export interface CodaCustomDomainParams extends CodaDocParams { + customDocDomain: string +} + +export interface CodaCustomDomainResponse extends ToolResponse { + output: { docId: string; customDocDomain: string } +} + +export interface CodaGetCustomDomainProviderParams extends CodaAuthParams { + customDocDomain: string +} + +export interface CodaGetCustomDomainProviderResponse extends ToolResponse { + output: { customDocDomain: string; provider: string } +} + +export interface CodaResolveBrowserLinkParams extends CodaAuthParams { + url: string + degradeGracefully?: boolean +} + +export interface CodaResolveBrowserLinkResponse extends ToolResponse { + output: { + browserLink: string | null + resource: { type: string; id: string; name: string | null; href: string } + } +} + +export interface CodaGetMutationStatusParams extends CodaAuthParams { + requestId: string +} + +export interface CodaGetMutationStatusResponse extends ToolResponse { + output: { completed: boolean; warning: string | null } +} + +export interface CodaTriggerAutomationParams extends CodaDocParams { + ruleId: string + payload?: unknown +} + +export type CodaResponse = + | CodaWhoamiResponse + | CodaListDocsResponse + | CodaDocResponse + | CodaCreateDocResponse + | CodaDocIdResponse + | CodaListCategoriesResponse + | CodaRequestIdResponse + | CodaSharingMetadataResponse + | CodaAclSettingsResponse + | CodaSearchPrincipalsResponse + | CodaListPermissionsResponse + | CodaAddPermissionResponse + | CodaDeletePermissionResponse + | CodaListPagesResponse + | CodaPageResponse + | CodaPageMutationResponse + | CodaGetPageContentResponse + | CodaExportPageResponse + | CodaPageExportStatusResponse + | CodaListTablesResponse + | CodaTableResponse + | CodaListColumnsResponse + | CodaColumnResponse + | CodaListRowsResponse + | CodaRowResponse + | CodaUpsertRowsResponse + | CodaRowMutationResponse + | CodaDeleteRowsResponse + | CodaPushButtonResponse + | CodaListFormulasResponse + | CodaFormulaResponse + | CodaListControlsResponse + | CodaControlResponse + | CodaListFoldersResponse + | CodaFolderResponse + | CodaDeleteFolderResponse + | CodaListFolderChildrenResponse + | CodaListWorkspaceMembersResponse + | CodaChangeUserRoleResponse + | CodaListWorkspaceRolesResponse + | CodaListDocAnalyticsResponse + | CodaListPageAnalyticsResponse + | CodaDocAnalyticsSummaryResponse + | CodaAnalyticsLastUpdatedResponse + | CodaListCustomDomainsResponse + | CodaCustomDomainResponse + | CodaGetCustomDomainProviderResponse + | CodaResolveBrowserLinkResponse + | CodaGetMutationStatusResponse diff --git a/apps/sim/tools/coda/unpublish_doc.ts b/apps/sim/tools/coda/unpublish_doc.ts new file mode 100644 index 00000000000..6527b3bc374 --- /dev/null +++ b/apps/sim/tools/coda/unpublish_doc.ts @@ -0,0 +1,39 @@ +import type { CodaDocIdResponse, CodaDocParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaUnpublishDocTool: ToolConfig = { + id: 'coda_unpublish_doc', + name: 'Coda Unpublish Doc', + description: 'Unpublish a Coda doc', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams, docId: DOC_ID_PARAM }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'publish')), + method: 'DELETE', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (_response, params) => ({ + success: true, + output: { docId: String(params?.docId ?? '').trim() }, + }), + + outputs: { + docId: { type: 'string', description: 'ID of the unpublished doc' }, + }, +} diff --git a/apps/sim/tools/coda/update_acl_settings.ts b/apps/sim/tools/coda/update_acl_settings.ts new file mode 100644 index 00000000000..17bb1a782e7 --- /dev/null +++ b/apps/sim/tools/coda/update_acl_settings.ts @@ -0,0 +1,84 @@ +import type { CodaAclSettingsParams, CodaAclSettingsResponse } from '@/tools/coda/types' +import { + ACL_SETTINGS_OUTPUTS, + buildCodaUrl, + CODA_FIELD_UPDATE_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +const SETTING_KEYS = [ + 'allowEditorsToChangePermissions', + 'allowCopying', + 'allowViewersToRequestEditing', +] as const + +export const codaUpdateAclSettingsTool: ToolConfig = + { + id: 'coda_update_acl_settings', + name: 'Coda Update Sharing Settings', + description: + 'Update who can change permissions, copy, or request edit access on a Coda doc; unset settings are left unchanged', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + allowEditorsToChangePermissions: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Allow editors to change doc permissions', + }, + allowCopying: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Allow viewers to copy the doc', + }, + allowViewersToRequestEditing: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Allow viewers to request edit access', + }, + }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'acl', 'settings')), + method: 'PATCH', + retry: CODA_FIELD_UPDATE_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const body: Record = {} + for (const key of SETTING_KEYS) { + if (typeof params[key] === 'boolean') body[key] = params[key] + } + if (Object.keys(body).length === 0) { + throw new Error('Provide at least one sharing setting to update') + } + return body + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as CodaAclSettingsResponse['output'] + return { + success: true, + output: { + allowEditorsToChangePermissions: data.allowEditorsToChangePermissions, + allowCopying: data.allowCopying, + allowViewersToRequestEditing: data.allowViewersToRequestEditing, + }, + } + }, + + outputs: ACL_SETTINGS_OUTPUTS, + } diff --git a/apps/sim/tools/coda/update_doc.ts b/apps/sim/tools/coda/update_doc.ts new file mode 100644 index 00000000000..2f19ffdb6a1 --- /dev/null +++ b/apps/sim/tools/coda/update_doc.ts @@ -0,0 +1,62 @@ +import type { CodaDocIdResponse, CodaUpdateDocParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_FIELD_UPDATE_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + optionalTrimmed, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaUpdateDocTool: ToolConfig = { + id: 'coda_update_doc', + name: 'Coda Update Doc', + description: + 'Rename a Coda doc or change its icon. Renaming requires Doc Maker access in the workspace.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + title: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New title of the doc', + }, + iconName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name of the icon to use (e.g., "rocket")', + }, + }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId)), + method: 'PATCH', + retry: CODA_FIELD_UPDATE_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const title = optionalTrimmed(params.title) + const iconName = optionalTrimmed(params.iconName) + if (!title && !iconName) throw new Error('Provide a title or iconName to update') + return { ...(title ? { title } : {}), ...(iconName ? { iconName } : {}) } + }, + }, + + transformResponse: async (_response, params) => ({ + success: true, + output: { docId: String(params?.docId ?? '').trim() }, + }), + + outputs: { + docId: { type: 'string', description: 'ID of the updated doc' }, + }, +} diff --git a/apps/sim/tools/coda/update_folder.ts b/apps/sim/tools/coda/update_folder.ts new file mode 100644 index 00000000000..f2f7b0df8d0 --- /dev/null +++ b/apps/sim/tools/coda/update_folder.ts @@ -0,0 +1,70 @@ +import type { CodaFolderResponse, CodaUpdateFolderParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_FIELD_UPDATE_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + codaPath, + FOLDER_ID_PARAM, + FOLDER_PROPERTIES, + mapFolder, + optionalTrimmed, + type RawCodaFolder, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaUpdateFolderTool: ToolConfig = { + id: 'coda_update_folder', + name: 'Coda Update Folder', + description: + 'Rename a Coda folder or change its description. Coda can return the folder as it was before the change; read it again to confirm.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + folderId: FOLDER_ID_PARAM, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name of the folder', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New description of the folder', + }, + }, + + request: { + url: (params) => buildCodaUrl(codaPath('folders', [params.folderId, 'folderId'])), + method: 'PATCH', + retry: CODA_FIELD_UPDATE_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const name = optionalTrimmed(params.name) + const description = params.description ? params.description : undefined + if (!name && description === undefined) { + throw new Error('Provide a name or description to update') + } + return { + ...(name ? { name } : {}), + ...(description !== undefined ? { description } : {}), + } + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawCodaFolder + return { success: true, output: { folder: mapFolder(data) } } + }, + + outputs: { + folder: { type: 'object', description: 'The updated folder', properties: FOLDER_PROPERTIES }, + }, +} diff --git a/apps/sim/tools/coda/update_page.ts b/apps/sim/tools/coda/update_page.ts new file mode 100644 index 00000000000..19644c869d6 --- /dev/null +++ b/apps/sim/tools/coda/update_page.ts @@ -0,0 +1,128 @@ +import type { CodaPageMutationResponse, CodaUpdatePageParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + optionalTrimmed, + PAGE_ID_PARAM, + REQUEST_ID_OUTPUT, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaUpdatePageTool: ToolConfig = { + id: 'coda_update_page', + name: 'Coda Update Page', + description: + 'Update a Coda page: rename it, change its subtitle, icon, cover, or visibility, and append, prepend, or replace content with Markdown or HTML. Applied asynchronously.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + pageId: PAGE_ID_PARAM, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name of the page', + }, + subtitle: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New subtitle of the page (an empty value leaves the subtitle unchanged)', + }, + iconName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name of the page icon (e.g., "rocket")', + }, + imageUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL of a cover image for the page', + }, + isHidden: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: + 'Whether the page is hidden (requires a paid Coda plan; ignored for pages that cannot be hidden)', + }, + insertionMode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'How to apply content: "append", "prepend", or "replace". Required when content is provided.', + }, + elementId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Page element to insert relative to or replace (e.g., "cl-lzqh0Q0poT"); omit to apply to the whole page', + }, + contentFormat: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Content format: "markdown" (default) or "html"', + }, + content: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Content to add to the page in the chosen format', + }, + }, + + request: { + url: (params) => buildCodaUrl(codaDocPath(params.docId, 'pages', [params.pageId, 'pageId'])), + method: 'PUT', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const body: Record = {} + if (optionalTrimmed(params.name)) body.name = optionalTrimmed(params.name) + if (params.subtitle) body.subtitle = params.subtitle + if (optionalTrimmed(params.iconName)) body.iconName = optionalTrimmed(params.iconName) + if (optionalTrimmed(params.imageUrl)) body.imageUrl = optionalTrimmed(params.imageUrl) + if (typeof params.isHidden === 'boolean') body.isHidden = params.isHidden + if (params.content) { + if (!params.insertionMode) { + throw new Error('insertionMode is required when updating page content') + } + const elementId = optionalTrimmed(params.elementId) + body.contentUpdate = { + insertionMode: params.insertionMode, + ...(elementId ? { elementId } : {}), + canvasContent: { format: params.contentFormat || 'markdown', content: params.content }, + } + } + if (Object.keys(body).length === 0) { + throw new Error('Provide at least one page property or content to update') + } + return body + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string; id: string } + return { success: true, output: { requestId: data.requestId, pageId: data.id } } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + pageId: { type: 'string', description: 'ID of the updated page' }, + }, +} diff --git a/apps/sim/tools/coda/update_row.ts b/apps/sim/tools/coda/update_row.ts new file mode 100644 index 00000000000..c044fcd75bd --- /dev/null +++ b/apps/sim/tools/coda/update_row.ts @@ -0,0 +1,75 @@ +import type { CodaRowMutationResponse, CodaUpdateRowParams } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + parseJsonInput, + REQUEST_ID_OUTPUT, + ROW_ID_PARAM, + TABLE_ID_PARAM, + toCodaCells, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaUpdateRowTool: ToolConfig = { + id: 'coda_update_row', + name: 'Coda Update Row', + description: 'Update cell values in a row of a Coda table. Applied asynchronously.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + tableId: TABLE_ID_PARAM, + rowId: ROW_ID_PARAM, + cells: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'Object mapping column IDs (or names) to new values, e.g., {"c-tuVwxYz": "Done"}, or Coda cells [{"column": "c-tuVwxYz", "value": "Done"}]', + }, + disableParsing: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Store values exactly as given without parsing them', + }, + }, + + request: { + url: (params) => + buildCodaUrl( + codaDocPath(params.docId, 'tables', [params.tableId, 'tableId'], 'rows', [ + params.rowId, + 'rowId', + ]), + { disableParsing: params.disableParsing } + ), + method: 'PUT', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const cells = toCodaCells(parseJsonInput(params.cells, 'cells'), 'cells') + if (cells.length === 0) throw new Error('cells must contain at least one column value') + return { row: { cells } } + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string; id: string } + return { success: true, output: { requestId: data.requestId, rowId: data.id } } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + rowId: { type: 'string', description: 'ID of the updated row' }, + }, +} diff --git a/apps/sim/tools/coda/upsert_rows.ts b/apps/sim/tools/coda/upsert_rows.ts new file mode 100644 index 00000000000..aa3e3be7b3f --- /dev/null +++ b/apps/sim/tools/coda/upsert_rows.ts @@ -0,0 +1,90 @@ +import type { CodaUpsertRowsParams, CodaUpsertRowsResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaDocPath, + codaHeaders, + codaOAuth, + DOC_ID_PARAM, + parseJsonInput, + parseStringList, + REQUEST_ID_OUTPUT, + TABLE_ID_PARAM, + toCodaCells, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +export const codaUpsertRowsTool: ToolConfig = { + id: 'coda_upsert_rows', + name: 'Coda Insert or Upsert Rows', + description: + 'Insert rows into a Coda base table, or update matching rows when key columns are given. Only works on base tables, not views. Applied asynchronously.', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { + ...codaAuthParams, + docId: DOC_ID_PARAM, + tableId: TABLE_ID_PARAM, + rows: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'Array of rows. Each row maps column IDs (or names) to values, e.g., [{"c-tuVwxYz": "Apple", "c-bCdeFgh": 12}], or uses Coda cells [{"cells": [{"column": "c-tuVwxYz", "value": "Apple"}]}]', + }, + keyColumns: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'Column IDs (or names) to match existing rows on, as an array or comma-separated list. Matching rows are updated instead of inserted.', + }, + disableParsing: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Store values exactly as given without parsing them', + }, + }, + + request: { + url: (params) => + buildCodaUrl(codaDocPath(params.docId, 'tables', [params.tableId, 'tableId'], 'rows'), { + disableParsing: params.disableParsing, + }), + method: 'POST', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken, true), + body: (params) => { + const parsed = parseJsonInput(params.rows, 'rows') + const rows = Array.isArray(parsed) ? parsed : parsed === undefined ? [] : [parsed] + if (rows.length === 0) throw new Error('rows must contain at least one row') + const keyColumns = parseStringList(params.keyColumns, 'keyColumns') + return { + rows: rows.map((row, index) => ({ cells: toCodaCells(row, `rows[${index}]`) })), + ...(keyColumns.length > 0 ? { keyColumns } : {}), + } + }, + }, + + transformResponse: async (response) => { + const data = (await response.json()) as { requestId: string; addedRowIds?: string[] } + return { + success: true, + output: { requestId: data.requestId, addedRowIds: data.addedRowIds ?? [] }, + } + }, + + outputs: { + requestId: REQUEST_ID_OUTPUT, + addedRowIds: { + type: 'array', + description: 'IDs of rows that will be added (only returned when no key columns are set)', + items: { type: 'string', description: 'Row ID' }, + }, + }, +} diff --git a/apps/sim/tools/coda/utils.ts b/apps/sim/tools/coda/utils.ts new file mode 100644 index 00000000000..bf44386e11a --- /dev/null +++ b/apps/sim/tools/coda/utils.ts @@ -0,0 +1,1106 @@ +import { omit } from '@sim/utils/object' +import type { + CodaColumn, + CodaControl, + CodaDoc, + CodaFolder, + CodaFormula, + CodaNamedReference, + CodaPage, + CodaPermission, + CodaRow, + CodaTable, + CodaTableReference, +} from '@/tools/coda/types' +import type { OutputProperty, ToolConfig, ToolRetryConfig } from '@/tools/types' +import { safeUrlPathSegment } from '@/tools/url-path' + +export const CODA_API_BASE = 'https://coda.io/apis/v1' + +type QueryValue = string | number | boolean | null | undefined + +/** + * Builds a Coda API URL from already-guarded path segments and optional query params. + * Empty query values are omitted so unset optional filters are never sent. A page token + * already encodes the original query, and Coda rejects any other parameter sent with it + * (for example `limit` on pages, or `useColumnNames` on rows), so only the token is sent. + */ +export function buildCodaUrl(path: string, query?: Record): string { + const url = new URL(`${CODA_API_BASE}${path}`) + const pageToken = query?.pageToken + const effectiveQuery = + typeof pageToken === 'string' && pageToken.trim() !== '' ? { pageToken } : (query ?? {}) + for (const [key, value] of Object.entries(effectiveQuery)) { + if (value === undefined || value === null) continue + if (typeof value === 'string' && value.trim() === '') continue + url.searchParams.set(key, String(value)) + } + return url.toString() +} + +/** + * Joins literal path segments with traversal-guarded, percent-encoded identifiers. + * A string is a literal segment; a `[value, paramName]` tuple is a caller-supplied id. + */ +export function codaPath(...segments: Array): string { + return segments + .map((segment) => + typeof segment === 'string' ? `/${segment}` : `/${safeUrlPathSegment(segment[0], segment[1])}` + ) + .join('') +} + +/** Guards and encodes a doc-scoped path: `/docs/{docId}` plus any extra segments. */ +export function codaDocPath(docId: string, ...segments: Array): string { + return codaPath('docs', [docId, 'docId'], ...segments) +} + +/** Headers for every Coda API request; shared with the credential validator. */ +export function codaHeaders(accessToken: string, hasBody = false): Record { + return { + Authorization: `Bearer ${accessToken}`, + Accept: 'application/json', + ...(hasBody ? { 'Content-Type': 'application/json' } : {}), + } +} + +/** Parses a JSON value that may arrive as a serialized string from a block input. */ +export function parseJsonInput(value: unknown, paramName: string): unknown { + if (typeof value !== 'string') return value + const trimmed = value.trim() + if (!trimmed) return undefined + try { + return JSON.parse(trimmed) + } catch { + throw new Error(`${paramName} must be valid JSON`) + } +} + +/** Normalizes a list given as an array, JSON array string, or comma-separated string. */ +export function parseStringList(value: unknown, paramName: string): string[] { + if (value === undefined || value === null || value === '') return [] + if (typeof value === 'string' && !value.trim().startsWith('[')) { + return value + .split(',') + .map((item) => item.trim()) + .filter(Boolean) + } + const parsed = parseJsonInput(value, paramName) + if (!Array.isArray(parsed)) { + throw new Error(`${paramName} must be an array or a comma-separated list`) + } + return parsed.map((item) => String(item).trim()).filter(Boolean) +} + +/** Joins a list param into Coda's comma-delimited query format, or undefined when empty. */ +export function joinListParam(value: unknown, paramName: string): string | undefined { + const items = parseStringList(value, paramName) + return items.length > 0 ? items.join(',') : undefined +} + +/** Trims an optional string param, returning undefined when blank. */ +export function optionalTrimmed(value: unknown): string | undefined { + if (value === undefined || value === null) return undefined + const trimmed = String(value).trim() + return trimmed || undefined +} + +/** + * Trims a required string param, throwing when it is blank. The executor's required check + * accepts whitespace, which would otherwise drop the value from the query string. + */ +export function requiredTrimmed(value: unknown, paramName: string): string { + const trimmed = optionalTrimmed(value) + if (!trimmed) throw new Error(`${paramName} is required`) + return trimmed +} + +interface CellEdit { + column: string + value: unknown +} + +function isCellArray(value: unknown): value is CellEdit[] { + return ( + Array.isArray(value) && + value.every( + (cell) => + cell !== null && + typeof cell === 'object' && + typeof (cell as { column?: unknown }).column === 'string' && + 'value' in cell + ) + ) +} + +/** + * Converts a row given either as Coda cells (`[{ column, value }]`), a `{ cells: [...] }` + * object, or a plain `{ columnIdOrName: value }` map into Coda's cell-edit array. An object + * is only read as the `cells` wrapper when that is its sole key and holds cell edits, so a + * column named `cells` still maps normally. + */ +export function toCodaCells(row: unknown, paramName: string): CellEdit[] { + if (isCellArray(row)) return row + if (row === null || typeof row !== 'object' || Array.isArray(row)) { + throw new Error(`${paramName} must be an object mapping columns to values`) + } + const entries = Object.entries(row) + if (entries.length === 1 && entries[0][0] === 'cells' && isCellArray(entries[0][1])) { + return entries[0][1] + } + return entries.map(([column, value]) => ({ column, value })) +} + +/** Builds Coda's `PageCreateContent` union from flat tool params, or undefined when absent. */ +export function buildPageCreateContent(params: { + pageType?: string + contentFormat?: string + content?: string + embedUrl?: string + renderMethod?: string + sourceDocId?: string + sourcePageId?: string + syncMode?: string + includeSubpages?: boolean +}): Record | undefined { + const pageType = params.pageType || 'canvas' + if (pageType === 'canvas') { + if (!params.content) return undefined + return { + type: 'canvas', + canvasContent: { format: params.contentFormat || 'markdown', content: params.content }, + } + } + if (pageType === 'embed') { + const url = optionalTrimmed(params.embedUrl) + if (!url) throw new Error('embedUrl is required when pageType is "embed"') + return { + type: 'embed', + url, + ...(params.renderMethod ? { renderMethod: params.renderMethod } : {}), + } + } + if (pageType === 'syncPage') { + const sourceDocId = optionalTrimmed(params.sourceDocId) + if (!sourceDocId) throw new Error('sourceDocId is required when pageType is "syncPage"') + if ((params.syncMode || 'page') === 'document') { + return { type: 'syncPage', mode: 'document', sourceDocId } + } + const sourcePageId = optionalTrimmed(params.sourcePageId) + if (!sourcePageId) throw new Error('sourcePageId is required for a single-page sync page') + return { + type: 'syncPage', + mode: 'page', + sourceDocId, + sourcePageId, + includeSubpages: params.includeSubpages === true, + } + } + throw new Error('pageType must be one of: canvas, embed, syncPage') +} + +export const codaAuthParams = { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'Coda API token resolved from the selected credential', + }, +} satisfies ToolConfig['params'] + +export const codaOAuth = { required: true, provider: 'coda' } as const + +/** + * Coda rate-limits per user and asks API clients to back off and retry on HTTP 429. Only + * idempotent methods retry, so a timed-out insert or page creation is never duplicated. + */ +export const CODA_RETRY = { + enabled: true, + maxRetries: 3, + initialDelayMs: 1_000, + maxDelayMs: 30_000, + retryIdempotentOnly: true, +} as const satisfies ToolRetryConfig + +/** + * Coda's PATCH endpoints set the supplied fields to fixed values, so repeating one is safe even + * though the executor does not treat PATCH as idempotent. + */ +export const CODA_FIELD_UPDATE_RETRY = { + ...CODA_RETRY, + retryIdempotentOnly: false, +} as const satisfies ToolRetryConfig + +interface RawReference { + id?: string + name?: string + href?: string + browserLink?: string + tableType?: string +} + +interface RawPerson { + name?: string + email?: string +} + +interface RawIcon { + name?: string + type?: string + browserLink?: string +} + +function mapIcon(icon: RawIcon | undefined) { + if (!icon) return null + return { + name: icon.name ?? null, + type: icon.type ?? null, + browserLink: icon.browserLink ?? null, + } +} + +function mapPerson(person: RawPerson | undefined) { + if (!person) return null + return { name: person.name ?? null, email: person.email ?? null } +} + +function mapPageRef(ref: RawReference | undefined) { + if (!ref?.id) return null + return { + id: ref.id, + name: ref.name ?? null, + href: ref.href ?? null, + browserLink: ref.browserLink ?? null, + } +} + +function mapTableRef(ref: RawReference | undefined) { + if (!ref?.id) return null + return { + id: ref.id, + name: ref.name ?? null, + tableType: ref.tableType ?? null, + href: ref.href ?? null, + browserLink: ref.browserLink ?? null, + } +} + +export interface RawCodaWorkspaceReference { + id?: string + name?: string + organizationId?: string + browserLink?: string +} + +export function mapWorkspaceRef(workspace: RawCodaWorkspaceReference | undefined) { + if (!workspace?.id) return null + return { + id: workspace.id, + name: workspace.name ?? null, + organizationId: workspace.organizationId ?? null, + browserLink: workspace.browserLink ?? null, + } +} + +export interface RawCodaDoc { + id: string + name: string + href: string + browserLink: string + icon?: RawIcon + owner?: string + ownerName?: string + createdAt?: string + updatedAt?: string + workspace?: RawCodaWorkspaceReference + folder?: RawReference + sourceDoc?: RawReference + docSize?: { + totalRowCount?: number + tableAndViewCount?: number + baseTableCount?: number + pageCount?: number + overApiSizeLimit?: boolean + } + published?: { + description?: string + browserLink?: string + imageLink?: string + discoverable?: boolean + earnCredit?: boolean + mode?: string + categories?: Array<{ name?: string }> + } +} + +export function mapDoc(doc: RawCodaDoc): CodaDoc { + return { + id: doc.id, + name: doc.name, + href: doc.href, + browserLink: doc.browserLink, + icon: mapIcon(doc.icon), + owner: doc.owner ?? null, + ownerName: doc.ownerName ?? null, + createdAt: doc.createdAt ?? null, + updatedAt: doc.updatedAt ?? null, + workspace: mapWorkspaceRef(doc.workspace), + folder: doc.folder?.id + ? { + id: doc.folder.id, + name: doc.folder.name ?? null, + browserLink: doc.folder.browserLink ?? null, + } + : null, + sourceDoc: doc.sourceDoc?.id + ? { + id: doc.sourceDoc.id, + href: doc.sourceDoc.href ?? null, + browserLink: doc.sourceDoc.browserLink ?? null, + } + : null, + docSize: doc.docSize + ? { + totalRowCount: doc.docSize.totalRowCount ?? null, + tableAndViewCount: doc.docSize.tableAndViewCount ?? null, + baseTableCount: doc.docSize.baseTableCount ?? null, + pageCount: doc.docSize.pageCount ?? null, + overApiSizeLimit: doc.docSize.overApiSizeLimit ?? null, + } + : null, + published: doc.published + ? { + description: doc.published.description ?? null, + browserLink: doc.published.browserLink ?? null, + imageLink: doc.published.imageLink ?? null, + discoverable: doc.published.discoverable ?? null, + earnCredit: doc.published.earnCredit ?? null, + mode: doc.published.mode ?? null, + categories: (doc.published.categories ?? []) + .map((category) => category.name) + .filter((name): name is string => typeof name === 'string'), + } + : null, + } +} + +export interface RawCodaPage { + id: string + name: string + subtitle?: string + href: string + browserLink: string + contentType?: string + isHidden?: boolean + isEffectivelyHidden?: boolean + icon?: RawIcon + image?: { browserLink?: string; type?: string; width?: number; height?: number } + parent?: RawReference + children?: RawReference[] + authors?: RawPerson[] + createdAt?: string + createdBy?: RawPerson + updatedAt?: string + updatedBy?: RawPerson +} + +export function mapPage(page: RawCodaPage): CodaPage { + return { + id: page.id, + name: page.name, + subtitle: page.subtitle ?? null, + href: page.href, + browserLink: page.browserLink, + contentType: page.contentType ?? null, + isHidden: page.isHidden ?? null, + isEffectivelyHidden: page.isEffectivelyHidden ?? null, + icon: mapIcon(page.icon), + image: page.image + ? { + browserLink: page.image.browserLink ?? null, + type: page.image.type ?? null, + width: page.image.width ?? null, + height: page.image.height ?? null, + } + : null, + parent: mapPageRef(page.parent), + children: (page.children ?? []).flatMap((child) => { + const mapped = mapPageRef(child) + return mapped ? [mapped] : [] + }), + authors: (page.authors ?? []).flatMap((author) => { + const mapped = mapPerson(author) + return mapped ? [mapped] : [] + }), + createdAt: page.createdAt ?? null, + createdBy: mapPerson(page.createdBy), + updatedAt: page.updatedAt ?? null, + updatedBy: mapPerson(page.updatedBy), + } +} + +export interface RawCodaTableReference { + id: string + name: string + tableType?: string + href: string + browserLink: string + parent?: RawReference +} + +export function mapTableReference(table: RawCodaTableReference): CodaTableReference { + return { + id: table.id, + name: table.name, + tableType: table.tableType ?? null, + href: table.href, + browserLink: table.browserLink, + parent: mapPageRef(table.parent), + } +} + +export interface RawCodaTable extends RawCodaTableReference { + parentTable?: RawReference + displayColumn?: RawReference + rowCount?: number + sorts?: Array<{ column?: RawReference; direction?: string }> + layout?: string + filter?: { + valid?: boolean + isVolatile?: boolean + hasUserFormula?: boolean + hasTodayFormula?: boolean + hasNowFormula?: boolean + } + createdAt?: string + updatedAt?: string +} + +export function mapTable(table: RawCodaTable): CodaTable { + return { + ...mapTableReference(table), + parentTable: mapTableRef(table.parentTable), + displayColumnId: table.displayColumn?.id ?? null, + rowCount: table.rowCount ?? null, + sorts: (table.sorts ?? []).map((sort) => ({ + columnId: sort.column?.id ?? null, + direction: sort.direction ?? null, + })), + layout: table.layout ?? null, + filter: table.filter + ? { + valid: table.filter.valid ?? null, + isVolatile: table.filter.isVolatile ?? null, + hasUserFormula: table.filter.hasUserFormula ?? null, + hasTodayFormula: table.filter.hasTodayFormula ?? null, + hasNowFormula: table.filter.hasNowFormula ?? null, + } + : null, + createdAt: table.createdAt ?? null, + updatedAt: table.updatedAt ?? null, + } +} + +export interface RawCodaColumn { + id: string + name: string + href: string + display?: boolean + calculated?: boolean + formula?: string + defaultValue?: string + format?: Record & { type?: string; isArray?: boolean } + parent?: RawReference +} + +export function mapColumn(column: RawCodaColumn): CodaColumn { + return { + id: column.id, + name: column.name, + href: column.href, + display: column.display ?? null, + calculated: column.calculated ?? null, + formula: column.formula ?? null, + defaultValue: column.defaultValue ?? null, + format: column.format ?? null, + parentTable: mapTableRef(column.parent), + } +} + +export interface RawCodaRow { + id: string + name: string + index?: number + href: string + browserLink: string + createdAt?: string + updatedAt?: string + values?: Record + parent?: RawReference +} + +export function mapRow(row: RawCodaRow): CodaRow { + return { + id: row.id, + name: row.name, + index: row.index ?? null, + href: row.href, + browserLink: row.browserLink, + createdAt: row.createdAt ?? null, + updatedAt: row.updatedAt ?? null, + values: row.values ?? {}, + parentTable: mapTableRef(row.parent), + } +} + +export interface RawCodaNamedReference { + id: string + name: string + href: string + parent?: RawReference +} + +export function mapNamedReference(item: RawCodaNamedReference): CodaNamedReference { + return { + id: item.id, + name: item.name, + href: item.href, + parent: mapPageRef(item.parent), + } +} + +export function mapFormula(item: RawCodaNamedReference & { value?: unknown }): CodaFormula { + return { ...mapNamedReference(item), value: item.value ?? null } +} + +export function mapControl( + item: RawCodaNamedReference & { controlType?: string; value?: unknown } +): CodaControl { + return { + ...mapNamedReference(item), + controlType: item.controlType ?? null, + value: item.value ?? null, + } +} + +export interface RawCodaFolder { + id: string + name?: string + browserLink?: string + description?: string + icon?: RawIcon + iconColor?: string + createdAt?: string + canEdit?: boolean + workspace?: RawCodaWorkspaceReference + visibility?: string +} + +export function mapFolder(folder: RawCodaFolder): CodaFolder { + return { + id: folder.id, + name: folder.name ?? null, + browserLink: folder.browserLink ?? null, + description: folder.description ?? null, + icon: mapIcon(folder.icon), + iconColor: folder.iconColor ?? null, + createdAt: folder.createdAt ?? null, + canEdit: folder.canEdit ?? null, + workspace: mapWorkspaceRef(folder.workspace), + } +} + +export interface RawCodaPermission { + id: string + access: string + principal?: { + type?: string + email?: string + groupId?: string + groupName?: string + domain?: string + workspaceId?: string + internalAccessType?: string + } +} + +export function mapPermission(permission: RawCodaPermission): CodaPermission { + const principal = permission.principal + return { + id: permission.id, + access: permission.access, + principal: { + type: principal?.type ?? null, + email: principal?.email ?? null, + groupId: principal?.groupId ?? null, + groupName: principal?.groupName ?? null, + domain: principal?.domain ?? null, + workspaceId: principal?.workspaceId ?? null, + internalAccessType: principal?.internalAccessType ?? null, + }, + } +} + +export const NEXT_PAGE_TOKEN_OUTPUT = { + type: 'string', + description: 'Token to pass as pageToken to fetch the next page of results', + nullable: true, +} as const satisfies OutputProperty + +export const REQUEST_ID_OUTPUT = { + type: 'string', + description: + 'Coda request ID for the queued change; pass to Get Mutation Status to confirm it was applied', +} as const satisfies OutputProperty + +export const ICON_PROPERTIES = { + name: { type: 'string', description: 'Icon name', nullable: true }, + type: { type: 'string', description: 'Icon MIME type', nullable: true }, + browserLink: { type: 'string', description: 'Link to the icon image', nullable: true }, +} as const satisfies Record + +const PERSON_PROPERTIES = { + name: { type: 'string', description: 'Full name', nullable: true }, + email: { type: 'string', description: 'Email address', nullable: true }, +} as const satisfies Record + +const PAGE_REF_PROPERTIES = { + id: { type: 'string', description: 'Page ID' }, + name: { type: 'string', description: 'Page name', nullable: true }, + href: { type: 'string', description: 'API link to the page', nullable: true }, + browserLink: { type: 'string', description: 'Browser link to the page', nullable: true }, +} as const satisfies Record + +const TABLE_REF_PROPERTIES = { + id: { type: 'string', description: 'Table ID' }, + name: { type: 'string', description: 'Table name', nullable: true }, + tableType: { type: 'string', description: 'Table type (table or view)', nullable: true }, + href: { type: 'string', description: 'API link to the table', nullable: true }, + browserLink: { type: 'string', description: 'Browser link to the table', nullable: true }, +} as const satisfies Record + +export const WORKSPACE_REF_PROPERTIES = { + id: { type: 'string', description: 'Workspace ID' }, + name: { type: 'string', description: 'Workspace name', nullable: true }, + organizationId: { + type: 'string', + description: 'Organization bound to the workspace', + nullable: true, + }, + browserLink: { type: 'string', description: 'Browser link to the workspace', nullable: true }, +} as const satisfies Record + +export const DOC_PROPERTIES = { + id: { type: 'string', description: 'Doc ID' }, + name: { type: 'string', description: 'Doc name' }, + href: { type: 'string', description: 'API link to the doc' }, + browserLink: { type: 'string', description: 'Browser link to the doc' }, + icon: { type: 'object', description: 'Doc icon', nullable: true, properties: ICON_PROPERTIES }, + owner: { type: 'string', description: 'Email address of the doc owner', nullable: true }, + ownerName: { type: 'string', description: 'Name of the doc owner', nullable: true }, + createdAt: { type: 'string', description: 'Creation timestamp', nullable: true }, + updatedAt: { type: 'string', description: 'Last modified timestamp', nullable: true }, + workspace: { + type: 'object', + description: 'Workspace containing the doc', + nullable: true, + properties: WORKSPACE_REF_PROPERTIES, + }, + folder: { + type: 'object', + description: 'Folder containing the doc', + nullable: true, + properties: { + id: { type: 'string', description: 'Folder ID' }, + name: { type: 'string', description: 'Folder name', nullable: true }, + browserLink: { type: 'string', description: 'Browser link to the folder', nullable: true }, + }, + }, + sourceDoc: { + type: 'object', + description: 'Doc this doc was copied from', + nullable: true, + properties: { + id: { type: 'string', description: 'Source doc ID' }, + href: { type: 'string', description: 'API link to the source doc', nullable: true }, + browserLink: { + type: 'string', + description: 'Browser link to the source doc', + nullable: true, + }, + }, + }, + docSize: { + type: 'object', + description: 'Size of the doc', + nullable: true, + properties: { + totalRowCount: { type: 'number', description: 'Rows across all tables', nullable: true }, + tableAndViewCount: { type: 'number', description: 'Tables and views', nullable: true }, + baseTableCount: { type: 'number', description: 'Base tables', nullable: true }, + pageCount: { type: 'number', description: 'Pages', nullable: true }, + overApiSizeLimit: { + type: 'boolean', + description: 'Whether the doc is over the API size limit', + nullable: true, + }, + }, + }, + published: { + type: 'object', + description: 'Publishing settings, when the doc is published', + nullable: true, + properties: { + description: { type: 'string', description: 'Published description', nullable: true }, + browserLink: { type: 'string', description: 'Published doc link', nullable: true }, + imageLink: { type: 'string', description: 'Cover image link', nullable: true }, + discoverable: { + type: 'boolean', + description: 'Whether the doc is discoverable', + nullable: true, + }, + earnCredit: { + type: 'boolean', + description: 'Whether viewers must sign in so the owner earns credit', + nullable: true, + }, + mode: { type: 'string', description: 'Interaction mode (view, play, edit)', nullable: true }, + categories: { + type: 'array', + description: 'Category names', + items: { type: 'string', description: 'Category name' }, + }, + }, + }, +} as const satisfies Record + +export const PAGE_PROPERTIES = { + id: { type: 'string', description: 'Page ID' }, + name: { type: 'string', description: 'Page name' }, + subtitle: { type: 'string', description: 'Page subtitle', nullable: true }, + href: { type: 'string', description: 'API link to the page' }, + browserLink: { type: 'string', description: 'Browser link to the page' }, + contentType: { + type: 'string', + description: 'Page type (canvas, embed, or syncPage)', + nullable: true, + }, + isHidden: { type: 'boolean', description: 'Whether the page is hidden', nullable: true }, + isEffectivelyHidden: { + type: 'boolean', + description: 'Whether the page or any parent is hidden', + nullable: true, + }, + icon: { type: 'object', description: 'Page icon', nullable: true, properties: ICON_PROPERTIES }, + image: { + type: 'object', + description: 'Cover image', + nullable: true, + properties: { + browserLink: { type: 'string', description: 'Image link', nullable: true }, + type: { type: 'string', description: 'Image MIME type', nullable: true }, + width: { type: 'number', description: 'Width in pixels', nullable: true }, + height: { type: 'number', description: 'Height in pixels', nullable: true }, + }, + }, + parent: { + type: 'object', + description: 'Parent page', + nullable: true, + properties: PAGE_REF_PROPERTIES, + }, + children: { + type: 'array', + description: 'Direct subpages', + items: { type: 'object', properties: PAGE_REF_PROPERTIES }, + }, + authors: { + type: 'array', + description: 'Page authors', + items: { type: 'object', properties: PERSON_PROPERTIES }, + }, + createdAt: { type: 'string', description: 'Creation timestamp', nullable: true }, + createdBy: { + type: 'object', + description: 'Page creator', + nullable: true, + properties: PERSON_PROPERTIES, + }, + updatedAt: { type: 'string', description: 'Last content update timestamp', nullable: true }, + updatedBy: { + type: 'object', + description: 'Last editor of the page', + nullable: true, + properties: PERSON_PROPERTIES, + }, +} as const satisfies Record + +export const TABLE_REFERENCE_PROPERTIES = { + id: { type: 'string', description: 'Table ID' }, + name: { type: 'string', description: 'Table name' }, + tableType: { + type: 'string', + description: 'Table type (table, view, or database)', + nullable: true, + }, + href: { type: 'string', description: 'API link to the table' }, + browserLink: { type: 'string', description: 'Browser link to the table' }, + parent: { + type: 'object', + description: 'Page containing the table', + nullable: true, + properties: PAGE_REF_PROPERTIES, + }, +} as const satisfies Record + +export const TABLE_PROPERTIES = { + ...TABLE_REFERENCE_PROPERTIES, + parentTable: { + type: 'object', + description: 'Base table, when this is a view', + nullable: true, + properties: TABLE_REF_PROPERTIES, + }, + displayColumnId: { type: 'string', description: 'Display column ID', nullable: true }, + rowCount: { type: 'number', description: 'Total number of rows', nullable: true }, + sorts: { + type: 'array', + description: 'Sorts applied to the table', + items: { + type: 'object', + properties: { + columnId: { type: 'string', description: 'Sorted column ID', nullable: true }, + direction: { type: 'string', description: 'ascending or descending', nullable: true }, + }, + }, + }, + layout: { + type: 'string', + description: 'Layout (default, card, calendar, detail, form, ganttChart, etc.)', + nullable: true, + }, + filter: { + type: 'object', + description: 'Details about the table filter formula, if any', + nullable: true, + properties: { + valid: { + type: 'boolean', + description: 'Whether the filter formula is valid', + nullable: true, + }, + isVolatile: { + type: 'boolean', + description: 'Whether results can differ by context or user', + nullable: true, + }, + hasUserFormula: { type: 'boolean', description: 'Uses User()', nullable: true }, + hasTodayFormula: { type: 'boolean', description: 'Uses Today()', nullable: true }, + hasNowFormula: { type: 'boolean', description: 'Uses Now()', nullable: true }, + }, + }, + createdAt: { type: 'string', description: 'Creation timestamp', nullable: true }, + updatedAt: { type: 'string', description: 'Last modified timestamp', nullable: true }, +} as const satisfies Record + +export const COLUMN_PROPERTIES = { + id: { type: 'string', description: 'Column ID' }, + name: { type: 'string', description: 'Column name' }, + href: { type: 'string', description: 'API link to the column' }, + display: { type: 'boolean', description: 'Whether this is the display column', nullable: true }, + calculated: { + type: 'boolean', + description: 'Whether the column has a formula', + nullable: true, + }, + formula: { type: 'string', description: 'Column formula', nullable: true }, + defaultValue: { type: 'string', description: 'Default value formula', nullable: true }, + format: { + type: 'json', + description: + 'Column format: always type (text, number, date, select, lookup, button, etc.) and isArray, plus type-specific settings such as precision, currencyCode, dateFormat, options, or the referenced table', + nullable: true, + }, + parentTable: { + type: 'object', + description: 'Table containing the column (returned by Get Column)', + nullable: true, + properties: TABLE_REF_PROPERTIES, + }, +} as const satisfies Record + +export const ROW_PROPERTIES = { + id: { type: 'string', description: 'Row ID' }, + name: { type: 'string', description: 'Row display name' }, + index: { type: 'number', description: 'Index of the row in the table', nullable: true }, + href: { type: 'string', description: 'API link to the row' }, + browserLink: { type: 'string', description: 'Browser link to the row' }, + createdAt: { type: 'string', description: 'Creation timestamp', nullable: true }, + updatedAt: { type: 'string', description: 'Last modified timestamp', nullable: true }, + values: { + type: 'json', + description: 'Cell values keyed by column ID (or column name when useColumnNames is set)', + }, + parentTable: { + type: 'object', + description: 'Table containing the row (returned by Get Row)', + nullable: true, + properties: TABLE_REF_PROPERTIES, + }, +} as const satisfies Record + +export const NAMED_REFERENCE_PROPERTIES = { + id: { type: 'string', description: 'ID' }, + name: { type: 'string', description: 'Name' }, + href: { type: 'string', description: 'API link' }, + parent: { + type: 'object', + description: 'Page containing the item', + nullable: true, + properties: PAGE_REF_PROPERTIES, + }, +} as const satisfies Record + +export const FOLDER_PROPERTIES = { + id: { type: 'string', description: 'Folder ID' }, + name: { type: 'string', description: 'Folder name', nullable: true }, + browserLink: { type: 'string', description: 'Browser link to the folder', nullable: true }, + description: { type: 'string', description: 'Folder description', nullable: true }, + icon: { + type: 'object', + description: 'Folder icon', + nullable: true, + properties: ICON_PROPERTIES, + }, + iconColor: { type: 'string', description: 'Folder icon color', nullable: true }, + createdAt: { type: 'string', description: 'Creation timestamp', nullable: true }, + canEdit: { + type: 'boolean', + description: 'Whether the folder settings can be edited', + nullable: true, + }, + workspace: { + type: 'object', + description: 'Workspace containing the folder', + nullable: true, + properties: WORKSPACE_REF_PROPERTIES, + }, +} as const satisfies Record + +/** Subfolders you cannot access carry only `id` and `visibility`, so their other fields are null. */ +export const FOLDER_CHILD_PROPERTIES = omit(FOLDER_PROPERTIES, ['icon']) + +export const PERMISSION_PROPERTIES = { + id: { type: 'string', description: 'Permission ID' }, + access: { type: 'string', description: 'Access level (readonly, write, comment, none)' }, + principal: { + type: 'object', + description: 'Who the permission is granted to', + properties: { + type: { + type: 'string', + description: 'Principal type (email, group, domain, workspace, anyone, internalAccess)', + nullable: true, + }, + email: { type: 'string', description: 'Email of an email principal', nullable: true }, + groupId: { type: 'string', description: 'Group ID of a group principal', nullable: true }, + groupName: { type: 'string', description: 'Name of a group principal', nullable: true }, + domain: { type: 'string', description: 'Domain of a domain principal', nullable: true }, + workspaceId: { + type: 'string', + description: 'Workspace ID of a workspace principal', + nullable: true, + }, + internalAccessType: { + type: 'string', + description: 'Internal access type (e.g., support)', + nullable: true, + }, + }, + }, +} as const satisfies Record + +export const DOC_ID_PARAM = { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the doc (e.g., "AbCDeFGH")', +} as const + +export const PAGE_ID_PARAM = { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'ID or name of the page (IDs are recommended, e.g., "canvas-IjkLmnO"; names containing "/" are not supported)', +} as const + +export const TABLE_ID_PARAM = { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'ID or name of the table or view (IDs are recommended, e.g., "grid-pqRst-U"; names containing "/" are not supported)', +} as const + +export const ROW_ID_PARAM = { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'ID or name of the row (IDs are recommended, e.g., "i-tuVwxYz"; names containing "/" are not supported)', +} as const + +export const WORKSPACE_ID_PARAM = { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the workspace (e.g., "ws-1Ab234")', +} as const + +export const FOLDER_ID_PARAM = { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'ID of the folder (e.g., "fl-1Ab234")', +} as const + +export const LIMIT_PARAM = { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return per page', +} as const + +export const PAGE_TOKEN_PARAM = { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous response to fetch the next page', +} as const + +export const SORT_BY_NAME_PARAM = { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order; "name" sorts alphabetically', +} as const + +export const CUSTOM_DOMAIN_PARAM = { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The custom domain (e.g., "docs.example.com")', +} as const + +export const ACL_SETTINGS_OUTPUTS = { + allowEditorsToChangePermissions: { + type: 'boolean', + description: 'Whether editors can change doc permissions (otherwise only the owner can)', + }, + allowCopying: { type: 'boolean', description: 'Whether viewers can copy the doc' }, + allowViewersToRequestEditing: { + type: 'boolean', + description: 'Whether viewers can request edit access', + }, +} as const satisfies Record diff --git a/apps/sim/tools/coda/whoami.ts b/apps/sim/tools/coda/whoami.ts new file mode 100644 index 00000000000..bce21f4432c --- /dev/null +++ b/apps/sim/tools/coda/whoami.ts @@ -0,0 +1,73 @@ +import type { CodaAuthParams, CodaWhoamiResponse } from '@/tools/coda/types' +import { + buildCodaUrl, + CODA_RETRY, + codaAuthParams, + codaHeaders, + codaOAuth, + mapWorkspaceRef, + type RawCodaWorkspaceReference, + WORKSPACE_REF_PROPERTIES, +} from '@/tools/coda/utils' +import { ErrorExtractorId } from '@/tools/error-extractors' +import type { ToolConfig } from '@/tools/types' + +interface RawWhoami { + name: string + loginId: string + pictureLink?: string + scoped?: boolean + tokenName?: string + workspace?: RawCodaWorkspaceReference +} + +export const codaWhoamiTool: ToolConfig = { + id: 'coda_whoami', + name: 'Coda Get Current User', + description: 'Get the user and default workspace behind the connected Coda API token', + version: '1.0.0', + oauth: codaOAuth, + errorExtractor: ErrorExtractorId.CODA_ERRORS, + + params: { ...codaAuthParams }, + + request: { + url: () => buildCodaUrl('/whoami'), + method: 'GET', + retry: CODA_RETRY, + headers: (params) => codaHeaders(params.accessToken), + }, + + transformResponse: async (response) => { + const data = (await response.json()) as RawWhoami + return { + success: true, + output: { + name: data.name, + loginId: data.loginId, + pictureLink: data.pictureLink ?? null, + scoped: data.scoped ?? null, + tokenName: data.tokenName ?? null, + workspace: mapWorkspaceRef(data.workspace), + }, + } + }, + + outputs: { + name: { type: 'string', description: 'Name of the user' }, + loginId: { type: 'string', description: 'Email address of the user' }, + pictureLink: { type: 'string', description: 'Link to the user avatar', nullable: true }, + scoped: { + type: 'boolean', + description: 'Whether the token is restricted to specific docs or tables', + nullable: true, + }, + tokenName: { type: 'string', description: 'Name of the API token', nullable: true }, + workspace: { + type: 'object', + description: 'Default workspace of the user', + nullable: true, + properties: WORKSPACE_REF_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/error-extractors.ts b/apps/sim/tools/error-extractors.ts index a033d6acf67..1177c38c642 100644 --- a/apps/sim/tools/error-extractors.ts +++ b/apps/sim/tools/error-extractors.ts @@ -50,6 +50,41 @@ interface ErrorExtractorConfig { redactData?: (errorInfo?: ErrorInfo) => unknown } +const CODA_MAX_VALIDATION_MESSAGES = 5 + +/** + * Flattens Coda's validation detail (`validationErrors` or nested schema `issues`) into + * `path: message` strings. Only Coda's own path and message text is used, never the + * submitted values. + */ +function collectCodaValidationMessages(detail: unknown): string[] { + const messages = new Set() + const visit = (issue: unknown) => { + if (messages.size >= CODA_MAX_VALIDATION_MESSAGES || !issue || typeof issue !== 'object') return + const record = issue as { path?: unknown; message?: unknown; errors?: unknown } + if (Array.isArray(record.errors) && record.errors.length > 0) { + for (const branch of record.errors) { + if (Array.isArray(branch)) branch.forEach(visit) + else visit(branch) + } + return + } + if (typeof record.message !== 'string' || !record.message) return + const path = Array.isArray(record.path) + ? record.path.filter((part) => typeof part === 'string' || typeof part === 'number').join('.') + : typeof record.path === 'string' + ? record.path + : '' + messages.add(path ? `${path}: ${record.message}` : record.message) + } + if (detail && typeof detail === 'object') { + const { validationErrors, issues } = detail as { validationErrors?: unknown; issues?: unknown } + if (Array.isArray(validationErrors)) validationErrors.forEach(visit) + if (Array.isArray(issues)) issues.forEach(visit) + } + return [...messages] +} + const PITCHBOOK_UNAUTHORIZED_MESSAGE = 'PitchBook rejected the API key. Check that the key is active and has API access.' @@ -234,6 +269,23 @@ const ERROR_EXTRACTORS: ErrorExtractorConfig[] = [ examples: ['Notion', 'Discord', 'GitHub', 'Twilio', 'Slack'], extract: (errorInfo) => errorInfo?.data?.message, }, + { + id: 'coda-errors', + description: + 'Coda (Superhuman Docs) API errors: the `message` field, or the field-level validation issues under `codaDetail` when the message is only the generic HTTP status text', + examples: ['Coda'], + extract: (errorInfo) => { + const data = errorInfo?.data + if (!data || typeof data !== 'object' || Array.isArray(data)) return undefined + const message = typeof data.message === 'string' ? data.message.trim() : '' + const generic = !message || message === data.statusMessage + if (!generic) return message + const details = collectCodaValidationMessages(data.codaDetail) + const status = message || (typeof data.statusMessage === 'string' ? data.statusMessage : '') + if (details.length > 0) return `${status || 'Invalid request'}: ${details.join('; ')}` + return status || undefined + }, + }, { id: 'harmonic-errors', description: @@ -618,6 +670,7 @@ export const ErrorExtractorId = { TELEGRAM_DESCRIPTION: 'telegram-description', STANDARD_MESSAGE: 'standard-message', HARMONIC_ERRORS: 'harmonic-errors', + CODA_ERRORS: 'coda-errors', SOAP_FAULT: 'soap-fault', OAUTH_ERROR_DESCRIPTION: 'oauth-error-description', NESTED_ERROR_OBJECT: 'nested-error-object', diff --git a/apps/sim/tools/generated/tool-ids.ts b/apps/sim/tools/generated/tool-ids.ts index 67aa132eb1a..4925d58cea9 100644 --- a/apps/sim/tools/generated/tool-ids.ts +++ b/apps/sim/tools/generated/tool-ids.ts @@ -3,7 +3,7 @@ /** Every registered tool id, including versioned variants. */ const toolIds: string[] = JSON.parse( - '["a2a_cancel_task","a2a_get_agent_card","a2a_get_task","a2a_send_message","affinity_batch_update_entity_fields","affinity_batch_update_list_entry_fields","affinity_create_list","affinity_create_list_field_dropdown_option","affinity_create_merge","affinity_create_note","affinity_create_reminder","affinity_delete_list_field_dropdown_option","affinity_delete_note","affinity_get_company","affinity_get_current_user","affinity_get_entity_field_value","affinity_get_list","affinity_get_list_entry","affinity_get_list_entry_field","affinity_get_list_field_dropdown_option","affinity_get_merge","affinity_get_merge_task","affinity_get_note","affinity_get_opportunity","affinity_get_person","affinity_get_saved_view","affinity_get_transcript","affinity_get_user","affinity_list_calls","affinity_list_chat_messages","affinity_list_companies","affinity_list_coworker_connections","affinity_list_emails","affinity_list_entity_field_values","affinity_list_entity_list_entries","affinity_list_entity_lists","affinity_list_entity_notes","affinity_list_entity_relationships","affinity_list_field_dropdown_options","affinity_list_field_metadata","affinity_list_field_value_changes","affinity_list_investor_executive_connections","affinity_list_list_entries","affinity_list_list_entry_field_value_changes","affinity_list_list_entry_fields","affinity_list_list_field_dropdown_options","affinity_list_list_fields","affinity_list_lists","affinity_list_meetings","affinity_list_merge_tasks","affinity_list_merges","affinity_list_note_attached_companies","affinity_list_note_attached_opportunities","affinity_list_note_attached_persons","affinity_list_note_replies","affinity_list_notes","affinity_list_opportunities","affinity_list_persons","affinity_list_reminders","affinity_list_saved_view_entries","affinity_list_saved_views","affinity_list_transcript_fragments","affinity_list_transcripts","affinity_list_users","affinity_search_companies","affinity_search_files","affinity_search_list_entries","affinity_search_notes","affinity_search_persons","affinity_semantic_search","affinity_update_entity_field_value","affinity_update_list_entry_field","affinity_update_list_field_dropdown_option","affinity_update_note","agentmail_create_draft","agentmail_create_inbox","agentmail_delete_draft","agentmail_delete_inbox","agentmail_delete_thread","agentmail_forward_message","agentmail_get_draft","agentmail_get_inbox","agentmail_get_message","agentmail_get_thread","agentmail_list_drafts","agentmail_list_inboxes","agentmail_list_messages","agentmail_list_threads","agentmail_reply_message","agentmail_send_draft","agentmail_send_message","agentmail_update_draft","agentmail_update_inbox","agentmail_update_message","agentmail_update_thread","agentphone_create_call","agentphone_create_contact","agentphone_create_number","agentphone_delete_contact","agentphone_get_call","agentphone_get_call_transcript","agentphone_get_contact","agentphone_get_conversation","agentphone_get_conversation_messages","agentphone_get_number_messages","agentphone_get_usage","agentphone_get_usage_daily","agentphone_get_usage_monthly","agentphone_list_calls","agentphone_list_contacts","agentphone_list_conversations","agentphone_list_numbers","agentphone_react_to_message","agentphone_release_number","agentphone_send_message","agentphone_update_contact","agentphone_update_conversation","agiloft_async_status","agiloft_attach_file","agiloft_attachment_info","agiloft_create_record","agiloft_delete_record","agiloft_get_choice_line_id","agiloft_list_tables","agiloft_lock_record","agiloft_nlp_search","agiloft_read_record","agiloft_remove_attachment","agiloft_retrieve_attachment","agiloft_run_action_button","agiloft_saved_search","agiloft_search_records","agiloft_select_records","agiloft_update_record","agiloft_upsert_record","ahrefs_anchors","ahrefs_backlinks","ahrefs_backlinks_stats","ahrefs_batch_analysis","ahrefs_broken_backlinks","ahrefs_domain_rating","ahrefs_domain_rating_history","ahrefs_keyword_overview","ahrefs_keywords_history","ahrefs_metrics","ahrefs_metrics_history","ahrefs_organic_competitors","ahrefs_organic_keywords","ahrefs_paid_pages","ahrefs_rank_tracker_competitors_overview","ahrefs_rank_tracker_competitors_stats","ahrefs_rank_tracker_overview","ahrefs_rank_tracker_serp_overview","ahrefs_refdomains_history","ahrefs_referring_domains","ahrefs_related_terms","ahrefs_site_audit_page_explorer","ahrefs_top_pages","airtable_create_records","airtable_delete_records","airtable_get_base_schema","airtable_get_record","airtable_list_bases","airtable_list_records","airtable_list_tables","airtable_update_multiple_records","airtable_update_record","airtable_upsert_records","airweave_search","algolia_add_record","algolia_batch_operations","algolia_browse_records","algolia_clear_records","algolia_copy_move_index","algolia_delete_by_filter","algolia_delete_index","algolia_delete_record","algolia_get_record","algolia_get_records","algolia_get_settings","algolia_get_task_status","algolia_list_indices","algolia_partial_update_record","algolia_search","algolia_update_settings","amplitude_event_segmentation","amplitude_funnels","amplitude_get_active_users","amplitude_get_revenue","amplitude_group_identify","amplitude_identify_user","amplitude_list_events","amplitude_realtime_active_users","amplitude_retention","amplitude_send_event","amplitude_user_activity","amplitude_user_profile","amplitude_user_search","apify_get_dataset_items","apify_get_run","apify_run_actor_async","apify_run_actor_sync","apify_run_task","apollo_account_bulk_create","apollo_account_bulk_update","apollo_account_create","apollo_account_search","apollo_account_update","apollo_contact_bulk_create","apollo_contact_bulk_update","apollo_contact_create","apollo_contact_search","apollo_contact_update","apollo_email_accounts","apollo_opportunity_create","apollo_opportunity_get","apollo_opportunity_search","apollo_opportunity_update","apollo_organization_bulk_enrich","apollo_organization_enrich","apollo_organization_search","apollo_people_bulk_enrich","apollo_people_enrich","apollo_people_search","apollo_sequence_add_contacts","apollo_sequence_search","apollo_task_create","apollo_task_search","appconfig_create_application","appconfig_create_configuration_profile","appconfig_create_environment","appconfig_create_hosted_configuration_version","appconfig_delete_application","appconfig_delete_configuration_profile","appconfig_delete_environment","appconfig_delete_hosted_configuration_version","appconfig_get_application","appconfig_get_configuration","appconfig_get_configuration_profile","appconfig_get_deployment","appconfig_get_environment","appconfig_get_hosted_configuration_version","appconfig_list_applications","appconfig_list_configuration_profiles","appconfig_list_deployment_strategies","appconfig_list_deployments","appconfig_list_environments","appconfig_list_hosted_configuration_versions","appconfig_start_deployment","appconfig_stop_deployment","appconfig_update_application","appconfig_update_configuration_profile","appconfig_update_environment","arxiv_get_author_papers","arxiv_get_paper","arxiv_search","asana_add_comment","asana_add_followers","asana_create_project","asana_create_section","asana_create_subtask","asana_create_task","asana_delete_task","asana_get_project","asana_get_projects","asana_get_task","asana_list_sections","asana_list_workspaces","asana_search_tasks","asana_update_task","ashby_add_candidate_tag","ashby_anonymize_candidate","ashby_change_application_source","ashby_change_application_stage","ashby_create_application","ashby_create_candidate","ashby_create_note","ashby_delete_application","ashby_get_application","ashby_get_candidate","ashby_get_job","ashby_get_job_posting","ashby_get_offer","ashby_get_opening","ashby_list_application_feedback","ashby_list_application_history","ashby_list_applications","ashby_list_archive_reasons","ashby_list_candidate_tags","ashby_list_candidates","ashby_list_custom_fields","ashby_list_departments","ashby_list_interview_plans","ashby_list_interview_stages","ashby_list_interviews","ashby_list_job_postings","ashby_list_jobs","ashby_list_locations","ashby_list_notes","ashby_list_offers","ashby_list_openings","ashby_list_sources","ashby_list_users","ashby_remove_candidate_tag","ashby_search_candidates","ashby_search_jobs","ashby_search_openings","ashby_search_users","ashby_set_custom_field_value","ashby_set_custom_field_values","ashby_transfer_application","ashby_update_candidate","ashby_upload_candidate_file","ashby_upload_resume","athena_batch_get_named_query","athena_batch_get_prepared_statement","athena_batch_get_query_execution","athena_create_named_query","athena_create_prepared_statement","athena_delete_named_query","athena_delete_prepared_statement","athena_get_data_catalog","athena_get_database","athena_get_named_query","athena_get_prepared_statement","athena_get_query_execution","athena_get_query_results","athena_get_query_runtime_statistics","athena_get_table_metadata","athena_get_work_group","athena_list_data_catalogs","athena_list_databases","athena_list_named_queries","athena_list_prepared_statements","athena_list_query_executions","athena_list_table_metadata","athena_list_work_groups","athena_start_query","athena_stop_query","athena_update_named_query","athena_update_prepared_statement","attio_assert_record","attio_create_attribute","attio_create_comment","attio_create_list","attio_create_list_entry","attio_create_note","attio_create_object","attio_create_record","attio_create_task","attio_create_webhook","attio_delete_comment","attio_delete_list_entry","attio_delete_note","attio_delete_record","attio_delete_task","attio_delete_webhook","attio_get_attribute","attio_get_comment","attio_get_list","attio_get_list_entry","attio_get_member","attio_get_note","attio_get_object","attio_get_record","attio_get_task","attio_get_thread","attio_get_webhook","attio_list_attributes","attio_list_lists","attio_list_members","attio_list_notes","attio_list_objects","attio_list_records","attio_list_tasks","attio_list_threads","attio_list_webhooks","attio_query_list_entries","attio_search_records","attio_update_attribute","attio_update_list","attio_update_list_entry","attio_update_object","attio_update_record","attio_update_task","attio_update_webhook","azure_data_explorer_create_table","azure_data_explorer_drop_table","azure_data_explorer_ingest_from_query","azure_data_explorer_ingest_inline","azure_data_explorer_list_databases","azure_data_explorer_list_functions","azure_data_explorer_list_tables","azure_data_explorer_management","azure_data_explorer_query","azure_data_explorer_show_database_schema","azure_data_explorer_show_ingestion_failures","azure_data_explorer_show_operations","azure_data_explorer_show_table_details","azure_data_explorer_show_table_schema","azure_devops_add_comment","azure_devops_create_work_item","azure_devops_get_build_log","azure_devops_get_build_timeline","azure_devops_get_comments","azure_devops_get_pipeline","azure_devops_get_pipeline_run","azure_devops_get_work_item","azure_devops_get_work_items_batch","azure_devops_get_work_items_between_builds","azure_devops_list_build_logs","azure_devops_list_builds","azure_devops_list_pipeline_runs","azure_devops_list_pipelines","azure_devops_query_work_items","azure_devops_update_work_item","bitbucket_approve_pull_request","bitbucket_create_branch","bitbucket_create_pull_request","bitbucket_create_pull_request_comment","bitbucket_decline_pull_request","bitbucket_delete_branch","bitbucket_get_commit","bitbucket_get_file","bitbucket_get_file_metadata","bitbucket_get_pipeline","bitbucket_get_pipeline_step_log","bitbucket_get_pull_request","bitbucket_get_pull_request_diff","bitbucket_get_pull_request_diffstat","bitbucket_get_pull_request_merge_task_status","bitbucket_get_repository","bitbucket_list_branches","bitbucket_list_commits","bitbucket_list_directory","bitbucket_list_pipeline_steps","bitbucket_list_pipelines","bitbucket_list_pull_request_comments","bitbucket_list_pull_request_commit_statuses","bitbucket_list_pull_requests","bitbucket_list_repositories","bitbucket_list_workspaces","bitbucket_merge_pull_request","bitbucket_request_pull_request_changes","bitbucket_stop_pipeline","bitbucket_trigger_pipeline","box_copy_file","box_create_folder","box_delete_file","box_delete_folder","box_download_file","box_download_file_v2","box_get_file_info","box_list_folder_items","box_search","box_sign_cancel_request","box_sign_create_request","box_sign_get_request","box_sign_list_requests","box_sign_resend_request","box_update_file","box_upload_file","brandfetch_get_brand","brandfetch_search","brex_archive_budget","brex_create_budget","brex_create_spend_limit","brex_create_transfer","brex_create_vendor","brex_get_budget","brex_get_cash_account","brex_get_company","brex_get_current_user","brex_get_expense","brex_get_spend_limit","brex_get_transfer","brex_get_user","brex_get_vendor","brex_list_budgets","brex_list_card_accounts","brex_list_card_statements","brex_list_card_transactions","brex_list_cards","brex_list_cash_accounts","brex_list_cash_statements","brex_list_cash_transactions","brex_list_departments","brex_list_expenses","brex_list_locations","brex_list_spend_limits","brex_list_titles","brex_list_transfers","brex_list_users","brex_list_vendors","brex_match_receipt","brex_update_expense","brex_update_vendor","brex_upload_receipt","brightdata_cancel_snapshot","brightdata_discover","brightdata_download_snapshot","brightdata_scrape_dataset","brightdata_scrape_url","brightdata_serp_search","brightdata_snapshot_status","brightdata_sync_scrape","browser_use_run_task","buffer_create_idea","buffer_create_post","buffer_delete_post","buffer_edit_post","buffer_get_account","buffer_get_channels","buffer_get_idea_groups","buffer_get_ideas","buffer_get_post","buffer_get_posts","calcom_cancel_booking","calcom_confirm_booking","calcom_create_booking","calcom_create_event_type","calcom_create_schedule","calcom_decline_booking","calcom_delete_event_type","calcom_delete_schedule","calcom_get_booking","calcom_get_default_schedule","calcom_get_event_type","calcom_get_schedule","calcom_get_slots","calcom_list_bookings","calcom_list_event_types","calcom_list_schedules","calcom_reschedule_booking","calcom_update_event_type","calcom_update_schedule","calendly_cancel_event","calendly_create_event_invitee","calendly_create_invitee_no_show","calendly_create_scheduling_link","calendly_create_webhook","calendly_delete_invitee_no_show","calendly_delete_webhook","calendly_get_current_user","calendly_get_event_invitee","calendly_get_event_type","calendly_get_scheduled_event","calendly_get_user","calendly_list_event_invitees","calendly_list_event_type_available_times","calendly_list_event_types","calendly_list_organization_memberships","calendly_list_routing_form_submissions","calendly_list_routing_forms","calendly_list_scheduled_events","calendly_list_user_availability_schedules","calendly_list_user_busy_times","calendly_list_webhooks","cbinsights_chat","cbinsights_get_commercial_maturity_history","cbinsights_get_exit_probability_history","cbinsights_get_mosaic_history","cbinsights_get_org_business_relationships","cbinsights_get_org_funding_window","cbinsights_get_org_fundings","cbinsights_get_org_investments","cbinsights_get_org_management_and_board","cbinsights_get_org_outlook","cbinsights_get_org_portfolio_exits","cbinsights_get_org_revenue","cbinsights_get_scouting_report","cbinsights_get_strategy_map","cbinsights_list_business_relationships","cbinsights_list_funding_window","cbinsights_list_fundings","cbinsights_list_investments","cbinsights_list_management_and_board","cbinsights_list_outlook","cbinsights_list_portfolio_exits","cbinsights_list_revenue","cbinsights_lookup_organizations","cbinsights_rag","cbinsights_search_firmographics","circleback_add_tag_to_meetings","circleback_create_tag","circleback_delete_action_item","circleback_delete_meeting","circleback_delete_tag","circleback_get_company","circleback_get_meeting","circleback_get_person","circleback_get_transcript","circleback_list_action_items","circleback_list_calendar_events","circleback_list_companies","circleback_list_meetings","circleback_list_people","circleback_list_tags","circleback_remove_tag_from_meetings","circleback_search_meetings","circleback_update_action_item","circleback_update_meeting","circleback_update_tag","clay_populate","clerk_add_organization_member","clerk_ban_user","clerk_create_actor_token","clerk_create_allowlist_identifier","clerk_create_blocklist_identifier","clerk_create_organization","clerk_create_organization_invitation","clerk_create_user","clerk_delete_allowlist_identifier","clerk_delete_blocklist_identifier","clerk_delete_organization","clerk_delete_user","clerk_get_jwt_template","clerk_get_organization","clerk_get_session","clerk_get_user","clerk_get_user_oauth_token","clerk_list_allowlist_identifiers","clerk_list_blocklist_identifiers","clerk_list_jwt_templates","clerk_list_organization_invitations","clerk_list_organization_memberships","clerk_list_organizations","clerk_list_sessions","clerk_list_users","clerk_lock_user","clerk_remove_organization_member","clerk_revoke_actor_token","clerk_revoke_session","clerk_unban_user","clerk_unlock_user","clerk_update_organization","clerk_update_organization_membership","clerk_update_user","clickhouse_count_rows","clickhouse_create_database","clickhouse_create_table","clickhouse_delete","clickhouse_describe_table","clickhouse_drop_database","clickhouse_drop_partition","clickhouse_drop_table","clickhouse_execute","clickhouse_insert","clickhouse_insert_rows","clickhouse_introspect","clickhouse_kill_query","clickhouse_list_clusters","clickhouse_list_databases","clickhouse_list_mutations","clickhouse_list_partitions","clickhouse_list_running_queries","clickhouse_list_tables","clickhouse_optimize_table","clickhouse_query","clickhouse_rename_table","clickhouse_show_create_table","clickhouse_table_stats","clickhouse_truncate_table","clickhouse_update","clickup_add_tag_to_task","clickup_create_checklist","clickup_create_checklist_item","clickup_create_comment","clickup_create_folder","clickup_create_list","clickup_create_task","clickup_create_time_entry","clickup_delete_checklist","clickup_delete_checklist_item","clickup_delete_comment","clickup_delete_task","clickup_delete_time_entry","clickup_get_comments","clickup_get_custom_fields","clickup_get_folders","clickup_get_list_members","clickup_get_lists","clickup_get_running_timer","clickup_get_space_tags","clickup_get_spaces","clickup_get_task","clickup_get_task_members","clickup_get_tasks","clickup_get_time_entries","clickup_get_workspaces","clickup_remove_custom_field_value","clickup_remove_tag_from_task","clickup_search_tasks","clickup_set_custom_field_value","clickup_start_timer","clickup_stop_timer","clickup_update_checklist","clickup_update_checklist_item","clickup_update_comment","clickup_update_task","clickup_update_time_entry","clickup_upload_attachment","cloudflare_create_access_application","cloudflare_create_access_policy","cloudflare_create_access_service_token","cloudflare_create_dns_record","cloudflare_create_r2_bucket","cloudflare_create_rate_limit_rule","cloudflare_create_ruleset","cloudflare_create_ruleset_rule","cloudflare_create_zone","cloudflare_delete_access_application","cloudflare_delete_access_policy","cloudflare_delete_dns_record","cloudflare_delete_r2_bucket","cloudflare_delete_ruleset_rule","cloudflare_delete_zone","cloudflare_dns_analytics","cloudflare_get_access_application","cloudflare_get_r2_bucket","cloudflare_get_ruleset","cloudflare_get_ruleset_entrypoint","cloudflare_get_tunnel","cloudflare_get_tunnel_configuration","cloudflare_get_worker_script_settings","cloudflare_get_zone","cloudflare_get_zone_settings","cloudflare_list_access_applications","cloudflare_list_access_groups","cloudflare_list_access_identity_providers","cloudflare_list_access_policies","cloudflare_list_access_service_tokens","cloudflare_list_certificates","cloudflare_list_dns_records","cloudflare_list_managed_ruleset_overrides","cloudflare_list_r2_buckets","cloudflare_list_rate_limit_rules","cloudflare_list_rulesets","cloudflare_list_tunnels","cloudflare_list_worker_routes","cloudflare_list_worker_scripts","cloudflare_list_zones","cloudflare_purge_cache","cloudflare_revoke_access_service_token","cloudflare_update_access_application","cloudflare_update_access_policy","cloudflare_update_dns_record","cloudflare_update_rate_limit_rule","cloudflare_update_ruleset_rule","cloudflare_update_zone_setting","cloudformation_cancel_update_stack","cloudformation_create_change_set","cloudformation_create_stack","cloudformation_delete_stack","cloudformation_describe_change_set","cloudformation_describe_stack_drift_detection_status","cloudformation_describe_stack_events","cloudformation_describe_stacks","cloudformation_detect_stack_drift","cloudformation_execute_change_set","cloudformation_get_template","cloudformation_get_template_summary","cloudformation_list_stack_resources","cloudformation_update_stack","cloudformation_validate_template","cloudtrail_cancel_query","cloudtrail_describe_query","cloudtrail_describe_trails","cloudtrail_get_event_data_store","cloudtrail_get_event_selectors","cloudtrail_get_insight_selectors","cloudtrail_get_query_results","cloudtrail_get_trail","cloudtrail_get_trail_status","cloudtrail_list_event_data_stores","cloudtrail_list_tags","cloudtrail_list_trails","cloudtrail_lookup_events","cloudtrail_start_query","cloudwatch_describe_alarm_history","cloudwatch_describe_alarms","cloudwatch_describe_log_groups","cloudwatch_describe_log_streams","cloudwatch_filter_log_events","cloudwatch_get_log_events","cloudwatch_get_metric_statistics","cloudwatch_list_metrics","cloudwatch_mute_alarm","cloudwatch_put_log_group_retention","cloudwatch_put_metric_data","cloudwatch_query_logs","cloudwatch_unmute_alarm","codepipeline_disable_stage_transition","codepipeline_enable_stage_transition","codepipeline_get_pipeline","codepipeline_get_pipeline_execution","codepipeline_get_pipeline_state","codepipeline_list_action_executions","codepipeline_list_pipeline_executions","codepipeline_list_pipelines","codepipeline_put_approval_result","codepipeline_retry_stage_execution","codepipeline_start_execution","codepipeline_stop_execution","confluence_add_label","confluence_create_blogpost","confluence_create_comment","confluence_create_page","confluence_create_page_property","confluence_create_space","confluence_create_space_property","confluence_delete_attachment","confluence_delete_blogpost","confluence_delete_comment","confluence_delete_label","confluence_delete_page","confluence_delete_page_property","confluence_delete_space","confluence_delete_space_property","confluence_get_blogpost","confluence_get_page_ancestors","confluence_get_page_children","confluence_get_page_descendants","confluence_get_page_version","confluence_get_pages_by_label","confluence_get_space","confluence_get_task","confluence_get_user","confluence_list_attachments","confluence_list_blogposts","confluence_list_blogposts_in_space","confluence_list_comments","confluence_list_labels","confluence_list_page_properties","confluence_list_page_versions","confluence_list_pages_in_space","confluence_list_space_labels","confluence_list_space_permissions","confluence_list_space_properties","confluence_list_spaces","confluence_list_tasks","confluence_retrieve","confluence_search","confluence_search_in_space","confluence_update","confluence_update_blogpost","confluence_update_comment","confluence_update_space","confluence_update_task","confluence_upload_attachment","context_dev_classify_naics","context_dev_classify_sic","context_dev_crawl","context_dev_extract","context_dev_extract_product","context_dev_extract_products","context_dev_get_brand","context_dev_get_brand_by_email","context_dev_get_brand_by_name","context_dev_get_brand_by_ticker","context_dev_identify_transaction","context_dev_map","context_dev_scrape_fonts","context_dev_scrape_html","context_dev_scrape_images","context_dev_scrape_markdown","context_dev_scrape_styleguide","context_dev_screenshot","context_dev_search","convex_action","convex_document_deltas","convex_list_documents","convex_list_tables","convex_mutation","convex_query","convex_run_function","crowdstrike_create_indicators","crowdstrike_delete_indicators","crowdstrike_delete_rtr_session","crowdstrike_execute_rtr_command","crowdstrike_get_alert_details","crowdstrike_get_case_details","crowdstrike_get_host_group_details","crowdstrike_get_indicator_details","crowdstrike_get_rtr_command_status","crowdstrike_get_sensor_aggregates","crowdstrike_get_sensor_details","crowdstrike_get_vulnerability_details","crowdstrike_init_rtr_session","crowdstrike_perform_host_action","crowdstrike_perform_host_group_action","crowdstrike_query_alerts","crowdstrike_query_cases","crowdstrike_query_host_groups","crowdstrike_query_indicators","crowdstrike_query_sensors","crowdstrike_query_vulnerabilities","crowdstrike_update_alerts","crowdstrike_update_indicators","crunchbase_autocomplete","crunchbase_get_acquisition","crunchbase_get_entity","crunchbase_get_entity_card","crunchbase_get_fields_metadata","crunchbase_get_funding_round","crunchbase_get_organization","crunchbase_get_person","crunchbase_list_deleted_entities","crunchbase_search_acquisitions","crunchbase_search_entities","crunchbase_search_funding_rounds","crunchbase_search_organizations","crunchbase_search_people","cursor_add_followup","cursor_add_followup_v2","cursor_delete_agent","cursor_delete_agent_v2","cursor_download_artifact","cursor_download_artifact_v2","cursor_get_agent","cursor_get_agent_v2","cursor_get_api_key_info","cursor_get_api_key_info_v2","cursor_get_conversation","cursor_get_conversation_v2","cursor_launch_agent","cursor_launch_agent_v2","cursor_list_agents","cursor_list_agents_v2","cursor_list_artifacts","cursor_list_artifacts_v2","cursor_list_models","cursor_list_models_v2","cursor_list_repositories","cursor_list_repositories_v2","cursor_stop_agent","cursor_stop_agent_v2","dagster_delete_run","dagster_get_asset","dagster_get_run","dagster_get_run_logs","dagster_launch_run","dagster_list_assets","dagster_list_jobs","dagster_list_runs","dagster_list_schedules","dagster_list_sensors","dagster_materialize_assets","dagster_reexecute_run","dagster_report_asset_materialization","dagster_start_schedule","dagster_start_sensor","dagster_stop_schedule","dagster_stop_sensor","dagster_terminate_run","dagster_wipe_asset","databricks_cancel_run","databricks_execute_sql","databricks_get_cluster","databricks_get_job","databricks_get_run","databricks_get_run_output","databricks_get_statement","databricks_list_clusters","databricks_list_jobs","databricks_list_runs","databricks_list_warehouses","databricks_run_job","datadog_add_incident_todo","datadog_cancel_downtime","datadog_create_dashboard","datadog_create_downtime","datadog_create_event","datadog_create_incident","datadog_create_monitor","datadog_create_slo","datadog_delete_dashboard","datadog_delete_slo","datadog_get_browser_synthetics_results","datadog_get_dashboard","datadog_get_incident","datadog_get_monitor","datadog_get_security_signal","datadog_get_slo","datadog_get_slo_history","datadog_get_synthetics_results","datadog_get_synthetics_test","datadog_list_dashboards","datadog_list_downtimes","datadog_list_incidents","datadog_list_monitors","datadog_list_security_rules","datadog_list_security_signals","datadog_list_services","datadog_list_slos","datadog_list_synthetics_tests","datadog_mute_monitor","datadog_query_logs","datadog_query_timeseries","datadog_search_spans","datadog_send_logs","datadog_submit_metrics","datadog_trigger_synthetics_tests","datadog_unmute_monitor","datadog_update_incident","datadog_update_security_signal_assignee","datadog_update_security_signal_state","datadog_update_slo","datadog_update_synthetics_status","datagma_enrich_company","datagma_enrich_person","datagma_find_email","datagma_find_phone","datagma_get_credits","daytona_create_sandbox","daytona_delete_sandbox","daytona_download_file","daytona_execute_command","daytona_get_sandbox","daytona_git_clone","daytona_list_files","daytona_list_sandboxes","daytona_run_code","daytona_start_sandbox","daytona_stop_sandbox","daytona_upload_file","deployed_block_executor","deployments_deploy","deployments_get_version","deployments_list_versions","deployments_promote","deployments_undeploy","devin_append_session_tags","devin_archive_session","devin_create_session","devin_get_session","devin_get_session_tags","devin_list_session_attachments","devin_list_session_messages","devin_list_sessions","devin_replace_session_tags","devin_send_message","devin_terminate_session","discord_add_reaction","discord_archive_thread","discord_assign_role","discord_ban_member","discord_bulk_delete_messages","discord_create_channel","discord_create_invite","discord_create_role","discord_create_thread","discord_create_webhook","discord_delete_channel","discord_delete_invite","discord_delete_message","discord_delete_role","discord_delete_webhook","discord_edit_message","discord_execute_webhook","discord_get_channel","discord_get_invite","discord_get_member","discord_get_messages","discord_get_pinned_messages","discord_get_server","discord_get_user","discord_get_webhook","discord_join_thread","discord_kick_member","discord_leave_thread","discord_list_channels","discord_list_roles","discord_pin_message","discord_remove_reaction","discord_remove_role","discord_send_message","discord_unban_member","discord_unpin_message","discord_update_channel","discord_update_member","discord_update_role","docusign_create_from_template","docusign_download_document","docusign_get_envelope","docusign_list_envelopes","docusign_list_recipients","docusign_list_templates","docusign_send_envelope","docusign_void_envelope","downdetector_get_company","downdetector_get_company_attribution","downdetector_get_company_baseline","downdetector_get_company_events","downdetector_get_company_incidents","downdetector_get_company_indicators","downdetector_get_company_last_15","downdetector_get_company_status","downdetector_get_provider","downdetector_get_reports","downdetector_get_site_companies","downdetector_list_categories","downdetector_list_incidents","downdetector_list_sites","downdetector_search_companies","dropbox_copy","dropbox_create_folder","dropbox_create_shared_link","dropbox_delete","dropbox_download","dropbox_download_v2","dropbox_get_metadata","dropbox_list_folder","dropbox_list_revisions","dropbox_list_shared_links","dropbox_move","dropbox_restore","dropbox_search","dropbox_upload","dropcontact_enrich_contact","dspy_chain_of_thought","dspy_predict","dspy_react","dub_bulk_create_links","dub_bulk_delete_links","dub_bulk_update_links","dub_create_link","dub_create_tag","dub_delete_link","dub_get_analytics","dub_get_events","dub_get_link","dub_get_links_count","dub_get_qr_code","dub_get_qr_code_v2","dub_list_domains","dub_list_folders","dub_list_links","dub_list_tags","dub_update_link","dub_upsert_link","duckduckgo_search","dynamodb_delete","dynamodb_get","dynamodb_introspect","dynamodb_put","dynamodb_query","dynamodb_scan","dynamodb_update","dynatrace_add_problem_comment","dynatrace_add_tags","dynatrace_close_problem","dynatrace_create_settings_object","dynatrace_create_slo","dynatrace_delete_problem_comment","dynatrace_delete_settings_object","dynatrace_delete_slo","dynatrace_delete_tag","dynatrace_execute_synthetic_monitors","dynatrace_get_attack","dynatrace_get_audit_logs","dynatrace_get_entity","dynatrace_get_event","dynatrace_get_metric","dynatrace_get_problem","dynatrace_get_problem_comment","dynatrace_get_security_problem","dynatrace_get_settings_object","dynatrace_get_slo","dynatrace_get_synthetic_batch","dynatrace_ingest_event","dynatrace_ingest_logs","dynatrace_ingest_metrics","dynatrace_list_attacks","dynatrace_list_entities","dynatrace_list_entity_types","dynatrace_list_events","dynatrace_list_metrics","dynatrace_list_problem_comments","dynatrace_list_problems","dynatrace_list_remediation_items","dynatrace_list_security_problems","dynatrace_list_settings_objects","dynatrace_list_settings_schemas","dynatrace_list_slos","dynatrace_list_synthetic_monitors","dynatrace_list_tags","dynatrace_mute_security_problem","dynatrace_mute_security_problems","dynatrace_query_metrics","dynatrace_search_logs","dynatrace_unmute_security_problem","dynatrace_unmute_security_problems","dynatrace_update_problem_comment","dynatrace_update_settings_object","dynatrace_update_slo","elasticsearch_bulk","elasticsearch_cluster_health","elasticsearch_cluster_stats","elasticsearch_count","elasticsearch_create_index","elasticsearch_delete_document","elasticsearch_delete_index","elasticsearch_get_document","elasticsearch_get_index","elasticsearch_index_document","elasticsearch_list_indices","elasticsearch_search","elasticsearch_update_document","elevenlabs_audio_isolation","elevenlabs_edit_voice_settings","elevenlabs_get_user","elevenlabs_get_voice","elevenlabs_get_voice_settings","elevenlabs_list_models","elevenlabs_list_voices","elevenlabs_sound_effects","elevenlabs_speech_to_speech","elevenlabs_tts","emailbison_attach_leads_to_campaign","emailbison_attach_tags_to_leads","emailbison_create_campaign","emailbison_create_lead","emailbison_create_tag","emailbison_get_lead","emailbison_list_campaigns","emailbison_list_leads","emailbison_list_replies","emailbison_list_tags","emailbison_update_campaign","emailbison_update_campaign_status","emailbison_update_lead","embeddings_cohere","embeddings_gemini","embeddings_mistral","embeddings_ollama","embeddings_openai","embeddings_openrouter","enrich_check_credits","enrich_company_funding","enrich_company_lookup","enrich_company_revenue","enrich_disposable_email_check","enrich_email_to_ip","enrich_email_to_person_lite","enrich_email_to_phone","enrich_email_to_profile","enrich_find_email","enrich_get_post_details","enrich_ip_to_company","enrich_linkedin_profile","enrich_linkedin_to_personal_email","enrich_linkedin_to_work_email","enrich_phone_finder","enrich_reverse_hash_lookup","enrich_sales_pointer_people","enrich_search_company","enrich_search_company_activities","enrich_search_company_employees","enrich_search_jobs","enrich_search_logo","enrich_search_people","enrich_search_people_activities","enrich_search_post_comments","enrich_search_post_comments_by_url","enrich_search_post_reactions","enrich_search_post_reactions_by_url","enrich_search_posts","enrich_search_similar_companies","enrich_verify_email","enrichment_run","enrow_find_email","enrow_verify_email","exa_agent","exa_answer","exa_find_similar_links","exa_get_contents","exa_search","extend_parser","extend_parser_v2","fathom_get_summary","fathom_get_transcript","fathom_list_meeting_types","fathom_list_meetings","fathom_list_team_members","fathom_list_teams","file_append","file_compress","file_create_folder","file_decompress","file_delete_folder","file_edit","file_fetch","file_get","file_get_content","file_list","file_manage_sharing","file_move","file_parser","file_parser_v2","file_parser_v3","file_read","file_restore_folder","file_search","file_update_folder","file_write","findymail_find_email_from_linkedin","findymail_find_email_from_name","findymail_find_emails_by_domain","findymail_find_employees","findymail_find_phone","findymail_get_company","findymail_get_credits","findymail_lookup_technologies","findymail_reverse_email_lookup","findymail_search_technologies","findymail_verify_email","firecrawl_agent","firecrawl_batch_scrape","firecrawl_batch_scrape_status","firecrawl_cancel_crawl","firecrawl_crawl","firecrawl_crawl_status","firecrawl_credit_usage","firecrawl_extract","firecrawl_extract_status","firecrawl_map","firecrawl_parse","firecrawl_scrape","firecrawl_search","fireflies_add_to_live_meeting","fireflies_create_bite","fireflies_delete_transcript","fireflies_get_transcript","fireflies_get_user","fireflies_list_bites","fireflies_list_contacts","fireflies_list_transcripts","fireflies_list_users","fireflies_upload_audio","flint_create_task","flint_generate_pages","flint_get_task","function_execute","gamma_check_status","gamma_generate","gamma_generate_from_template","gamma_list_folders","gamma_list_themes","github_add_assignees","github_add_assignees_v2","github_add_labels","github_add_labels_v2","github_cancel_workflow_run","github_cancel_workflow_run_v2","github_check_star","github_check_star_v2","github_close_issue","github_close_issue_v2","github_close_pr","github_close_pr_v2","github_comment","github_comment_v2","github_compare_commits","github_compare_commits_v2","github_create_branch","github_create_branch_v2","github_create_comment_reaction","github_create_comment_reaction_v2","github_create_file","github_create_file_v2","github_create_gist","github_create_gist_v2","github_create_issue","github_create_issue_reaction","github_create_issue_reaction_v2","github_create_issue_v2","github_create_milestone","github_create_milestone_v2","github_create_pr","github_create_pr_review","github_create_pr_review_v2","github_create_pr_v2","github_create_project","github_create_project_v2","github_create_release","github_create_release_v2","github_delete_branch","github_delete_branch_v2","github_delete_comment","github_delete_comment_reaction","github_delete_comment_reaction_v2","github_delete_comment_v2","github_delete_file","github_delete_file_v2","github_delete_gist","github_delete_gist_v2","github_delete_issue_reaction","github_delete_issue_reaction_v2","github_delete_milestone","github_delete_milestone_v2","github_delete_project","github_delete_project_v2","github_delete_release","github_delete_release_v2","github_fork_gist","github_fork_gist_v2","github_fork_repo","github_fork_repo_v2","github_get_branch","github_get_branch_protection","github_get_branch_protection_v2","github_get_branch_v2","github_get_commit","github_get_commit_v2","github_get_file_content","github_get_file_content_v2","github_get_gist","github_get_gist_v2","github_get_issue","github_get_issue_v2","github_get_latest_release","github_get_latest_release_v2","github_get_milestone","github_get_milestone_v2","github_get_pr_files","github_get_pr_files_v2","github_get_project","github_get_project_v2","github_get_readme","github_get_readme_v2","github_get_release","github_get_release_v2","github_get_tree","github_get_tree_v2","github_get_workflow","github_get_workflow_run","github_get_workflow_run_v2","github_get_workflow_v2","github_issue_comment","github_issue_comment_v2","github_job_logs","github_latest_commit","github_latest_commit_v2","github_list_branches","github_list_branches_v2","github_list_commits","github_list_commits_v2","github_list_forks","github_list_forks_v2","github_list_gists","github_list_gists_v2","github_list_issue_comments","github_list_issue_comments_v2","github_list_issues","github_list_issues_v2","github_list_milestones","github_list_milestones_v2","github_list_pr_comments","github_list_pr_comments_v2","github_list_projects","github_list_projects_v2","github_list_prs","github_list_prs_v2","github_list_releases","github_list_releases_v2","github_list_review_threads","github_list_stargazers","github_list_stargazers_v2","github_list_tags","github_list_tags_v2","github_list_workflow_runs","github_list_workflow_runs_v2","github_list_workflows","github_list_workflows_v2","github_merge_pr","github_merge_pr_v2","github_pr","github_pr_v2","github_remove_label","github_remove_label_v2","github_reply_review_thread","github_repo_info","github_repo_info_v2","github_request_reviewers","github_request_reviewers_v2","github_rerun_workflow","github_rerun_workflow_v2","github_resolve_review_thread","github_search_code","github_search_code_v2","github_search_commits","github_search_commits_v2","github_search_issues","github_search_issues_v2","github_search_repos","github_search_repos_v2","github_search_users","github_search_users_v2","github_star_gist","github_star_gist_v2","github_star_repo","github_star_repo_v2","github_status_check_rollup","github_trigger_workflow","github_trigger_workflow_v2","github_unstar_gist","github_unstar_gist_v2","github_unstar_repo","github_unstar_repo_v2","github_update_branch_protection","github_update_branch_protection_v2","github_update_comment","github_update_comment_v2","github_update_file","github_update_file_v2","github_update_gist","github_update_gist_v2","github_update_issue","github_update_issue_v2","github_update_milestone","github_update_milestone_v2","github_update_pr","github_update_pr_v2","github_update_project","github_update_project_v2","github_update_release","github_update_release_v2","gitlab_activate_user","gitlab_add_member","gitlab_add_saml_group_link","gitlab_approve_access_request","gitlab_approve_merge_request","gitlab_approve_user","gitlab_ban_user","gitlab_block_user","gitlab_cancel_pipeline","gitlab_compare_branches","gitlab_create_branch","gitlab_create_file","gitlab_create_issue","gitlab_create_issue_note","gitlab_create_merge_request","gitlab_create_merge_request_note","gitlab_create_pipeline","gitlab_create_release","gitlab_create_user","gitlab_deactivate_user","gitlab_delete_branch","gitlab_delete_issue","gitlab_delete_saml_group_link","gitlab_delete_user","gitlab_delete_user_identity","gitlab_deny_access_request","gitlab_get_file","gitlab_get_group","gitlab_get_issue","gitlab_get_job_log","gitlab_get_merge_request","gitlab_get_merge_request_changes","gitlab_get_pipeline","gitlab_get_project","gitlab_invite_member","gitlab_list_access_requests","gitlab_list_branches","gitlab_list_commits","gitlab_list_groups","gitlab_list_invitations","gitlab_list_issues","gitlab_list_members","gitlab_list_merge_requests","gitlab_list_pipeline_jobs","gitlab_list_pipelines","gitlab_list_projects","gitlab_list_releases","gitlab_list_repository_tree","gitlab_list_saml_group_links","gitlab_list_user_memberships","gitlab_merge_merge_request","gitlab_play_job","gitlab_reject_user","gitlab_remove_member","gitlab_retry_pipeline","gitlab_revoke_invitation","gitlab_search_users","gitlab_unban_user","gitlab_unblock_user","gitlab_update_file","gitlab_update_invitation","gitlab_update_issue","gitlab_update_member","gitlab_update_merge_request","gitlab_update_user","gmail_add_label","gmail_add_label_v2","gmail_archive","gmail_archive_v2","gmail_create_label_v2","gmail_delete","gmail_delete_draft_v2","gmail_delete_label_v2","gmail_delete_v2","gmail_draft","gmail_draft_v2","gmail_edit_draft_v2","gmail_get_draft_v2","gmail_get_thread_v2","gmail_list_drafts_v2","gmail_list_labels_v2","gmail_list_threads_v2","gmail_mark_read","gmail_mark_read_v2","gmail_mark_unread","gmail_mark_unread_v2","gmail_move","gmail_move_v2","gmail_read","gmail_read_v2","gmail_remove_label","gmail_remove_label_v2","gmail_search","gmail_search_v2","gmail_send","gmail_send_v2","gmail_trash_thread_v2","gmail_unarchive","gmail_unarchive_v2","gmail_untrash_thread_v2","gmail_update_label_v2","gong_aggregate_activity","gong_aggregate_by_period","gong_answered_scorecards","gong_ask_anything","gong_assign_flow_prospects","gong_create_call","gong_day_by_day_activity","gong_get_brief","gong_get_call","gong_get_call_transcript","gong_get_coaching","gong_get_extensive_calls","gong_get_folder_content","gong_get_logs","gong_get_prospect_flows","gong_get_user","gong_interaction_stats","gong_list_calls","gong_list_flows","gong_list_library_folders","gong_list_scorecards","gong_list_trackers","gong_list_users","gong_list_workspaces","gong_lookup_email","gong_lookup_phone","gong_purge_email_address","gong_purge_phone_number","gong_unassign_flow_prospects","google_ads_ad_performance","google_ads_campaign_performance","google_ads_list_ad_groups","google_ads_list_campaigns","google_ads_list_customers","google_ads_search","google_appsheet_add_rows","google_appsheet_delete_rows","google_appsheet_edit_rows","google_appsheet_find_rows","google_bigquery_create_dataset","google_bigquery_create_table","google_bigquery_delete_dataset","google_bigquery_delete_table","google_bigquery_get_query_results","google_bigquery_get_table","google_bigquery_insert_rows","google_bigquery_list_datasets","google_bigquery_list_table_data","google_bigquery_list_tables","google_bigquery_query","google_books_volume_details","google_books_volume_search","google_calendar_create","google_calendar_create_calendar","google_calendar_create_calendar_v2","google_calendar_create_v2","google_calendar_delete","google_calendar_delete_calendar","google_calendar_delete_calendar_v2","google_calendar_delete_v2","google_calendar_freebusy","google_calendar_freebusy_v2","google_calendar_get","google_calendar_get_v2","google_calendar_instances","google_calendar_instances_v2","google_calendar_invite","google_calendar_invite_v2","google_calendar_list","google_calendar_list_acl","google_calendar_list_acl_v2","google_calendar_list_calendars","google_calendar_list_calendars_v2","google_calendar_list_v2","google_calendar_move","google_calendar_move_v2","google_calendar_quick_add","google_calendar_quick_add_v2","google_calendar_share_calendar","google_calendar_share_calendar_v2","google_calendar_unshare_calendar","google_calendar_unshare_calendar_v2","google_calendar_update","google_calendar_update_acl","google_calendar_update_acl_v2","google_calendar_update_calendar","google_calendar_update_calendar_v2","google_calendar_update_v2","google_contacts_create","google_contacts_delete","google_contacts_get","google_contacts_list","google_contacts_search","google_contacts_update","google_docs_create","google_docs_create_named_range","google_docs_create_paragraph_bullets","google_docs_delete_content_range","google_docs_delete_named_range","google_docs_delete_paragraph_bullets","google_docs_insert_image","google_docs_insert_page_break","google_docs_insert_table","google_docs_insert_text","google_docs_read","google_docs_replace_text","google_docs_update_paragraph_style","google_docs_update_text_style","google_docs_write","google_drive_copy","google_drive_create_comment","google_drive_create_folder","google_drive_delete","google_drive_delete_comment","google_drive_download","google_drive_export","google_drive_get_about","google_drive_get_content","google_drive_get_file","google_drive_get_revision","google_drive_list","google_drive_list_comments","google_drive_list_permissions","google_drive_list_revisions","google_drive_move","google_drive_search","google_drive_share","google_drive_trash","google_drive_unshare","google_drive_untrash","google_drive_update","google_drive_upload","google_forms_batch_update","google_forms_create_form","google_forms_create_watch","google_forms_delete_watch","google_forms_get_form","google_forms_get_responses","google_forms_list_watches","google_forms_renew_watch","google_forms_set_publish_settings","google_groups_add_alias","google_groups_add_member","google_groups_create_group","google_groups_delete_group","google_groups_get_group","google_groups_get_member","google_groups_get_settings","google_groups_has_member","google_groups_list_aliases","google_groups_list_groups","google_groups_list_members","google_groups_remove_alias","google_groups_remove_member","google_groups_update_group","google_groups_update_member","google_groups_update_settings","google_maps_air_quality","google_maps_directions","google_maps_distance_matrix","google_maps_elevation","google_maps_geocode","google_maps_geolocate","google_maps_place_details","google_maps_places_nearby","google_maps_places_search","google_maps_pollen","google_maps_reverse_geocode","google_maps_snap_to_roads","google_maps_solar","google_maps_speed_limits","google_maps_timezone","google_maps_validate_address","google_meet_create_space","google_meet_end_conference","google_meet_get_conference_record","google_meet_get_space","google_meet_list_conference_records","google_meet_list_participants","google_pagespeed_analyze","google_search","google_sheets_append","google_sheets_append_v2","google_sheets_batch_clear_v2","google_sheets_batch_get_v2","google_sheets_batch_update_v2","google_sheets_clear_v2","google_sheets_copy_sheet_v2","google_sheets_create_spreadsheet_v2","google_sheets_delete_rows_v2","google_sheets_delete_sheet_v2","google_sheets_delete_spreadsheet_v2","google_sheets_get_spreadsheet_v2","google_sheets_read","google_sheets_read_v2","google_sheets_update","google_sheets_update_v2","google_sheets_write","google_sheets_write_v2","google_slides_add_image","google_slides_add_slide","google_slides_batch_update","google_slides_copy_presentation","google_slides_create","google_slides_create_line","google_slides_create_paragraph_bullets","google_slides_create_shape","google_slides_create_sheets_chart","google_slides_create_table","google_slides_create_video","google_slides_delete_object","google_slides_delete_paragraph_bullets","google_slides_delete_table_column","google_slides_delete_table_row","google_slides_delete_text","google_slides_duplicate_object","google_slides_export_presentation","google_slides_get_page","google_slides_get_thumbnail","google_slides_group_objects","google_slides_insert_table_columns","google_slides_insert_table_rows","google_slides_insert_text","google_slides_merge_table_cells","google_slides_read","google_slides_refresh_sheets_chart","google_slides_replace_all_shapes_with_image","google_slides_replace_all_shapes_with_sheets_chart","google_slides_replace_all_text","google_slides_replace_image","google_slides_reroute_line","google_slides_ungroup_objects","google_slides_unmerge_table_cells","google_slides_update_image_properties","google_slides_update_line_category","google_slides_update_line_properties","google_slides_update_page_element_alt_text","google_slides_update_page_element_transform","google_slides_update_page_elements_z_order","google_slides_update_page_properties","google_slides_update_paragraph_style","google_slides_update_shape_properties","google_slides_update_slide_properties","google_slides_update_slides_position","google_slides_update_table_border_properties","google_slides_update_table_cell_properties","google_slides_update_table_column_properties","google_slides_update_table_row_properties","google_slides_update_text_style","google_slides_update_video_properties","google_slides_write","google_tasks_create","google_tasks_delete","google_tasks_get","google_tasks_list","google_tasks_list_task_lists","google_tasks_update","google_translate_detect","google_translate_text","google_vault_add_held_accounts","google_vault_add_matters_permissions","google_vault_close_matters","google_vault_create_matters","google_vault_create_matters_export","google_vault_create_matters_holds","google_vault_create_saved_query","google_vault_delete_matters","google_vault_delete_matters_export","google_vault_delete_matters_holds","google_vault_delete_saved_query","google_vault_download_export_file","google_vault_list_matters","google_vault_list_matters_export","google_vault_list_matters_holds","google_vault_list_saved_queries","google_vault_remove_held_accounts","google_vault_remove_matters_permissions","google_vault_reopen_matters","google_vault_undelete_matters","google_vault_update_matters","google_vault_update_matters_holds","grafana_check_data_source_health","grafana_create_alert_rule","grafana_create_annotation","grafana_create_contact_point","grafana_create_dashboard","grafana_create_folder","grafana_delete_alert_rule","grafana_delete_annotation","grafana_delete_contact_point","grafana_delete_dashboard","grafana_delete_folder","grafana_get_alert_rule","grafana_get_alert_rule_group","grafana_get_dashboard","grafana_get_data_source","grafana_get_folder","grafana_get_health","grafana_list_alert_rules","grafana_list_annotations","grafana_list_contact_points","grafana_list_dashboards","grafana_list_data_sources","grafana_list_folders","grafana_move_folder","grafana_query_data_source","grafana_update_alert_rule","grafana_update_annotation","grafana_update_contact_point","grafana_update_dashboard","grafana_update_folder","grain_create_hook","grain_create_hook_v2","grain_delete_hook","grain_delete_hook_v2","grain_get_recording","grain_get_transcript","grain_list_hooks","grain_list_hooks_v2","grain_list_meeting_types","grain_list_recordings","grain_list_teams","grain_list_views","granola_create_webhook_endpoint","granola_delete_webhook_endpoint","granola_get_note","granola_get_transcript","granola_list_audit_events","granola_list_folders","granola_list_notes","granola_list_webhook_endpoints","granola_update_webhook_endpoint","greenhouse_get_application","greenhouse_get_candidate","greenhouse_get_job","greenhouse_get_user","greenhouse_list_applications","greenhouse_list_candidates","greenhouse_list_departments","greenhouse_list_job_stages","greenhouse_list_jobs","greenhouse_list_offices","greenhouse_list_users","greptile_index_repo","greptile_query","greptile_search","greptile_status","guardrails_validate","harmonic_batch_get_people","harmonic_clear_people_saved_search_net_new_results","harmonic_enrich_person","harmonic_get_company_employees","harmonic_get_email_enrichment_job","harmonic_get_email_enrichment_usage","harmonic_get_enrichment_status","harmonic_get_people_saved_search_net_new_results","harmonic_get_people_saved_search_results","harmonic_get_person","harmonic_list_people_saved_searches","harmonic_search_people_scout","harmonic_submit_email_enrichment_job","hex_cancel_run","hex_create_collection","hex_create_group","hex_deactivate_user","hex_delete_group","hex_get_collection","hex_get_data_connection","hex_get_group","hex_get_project","hex_get_project_runs","hex_get_queried_tables","hex_get_run_status","hex_list_collections","hex_list_data_connections","hex_list_groups","hex_list_projects","hex_list_users","hex_run_project","hex_update_collection","hex_update_group","hex_update_project","http_request","hubspot_add_list_memberships","hubspot_create_appointment","hubspot_create_association","hubspot_create_company","hubspot_create_contact","hubspot_create_deal","hubspot_create_email","hubspot_create_line_item","hubspot_create_list","hubspot_create_note","hubspot_create_ticket","hubspot_delete_association","hubspot_delete_company","hubspot_delete_contact","hubspot_delete_deal","hubspot_delete_line_item","hubspot_delete_ticket","hubspot_get_appointment","hubspot_get_association_labels","hubspot_get_cart","hubspot_get_company","hubspot_get_contact","hubspot_get_deal","hubspot_get_email","hubspot_get_line_item","hubspot_get_list","hubspot_get_list_memberships","hubspot_get_marketing_event","hubspot_get_note","hubspot_get_properties","hubspot_get_quote","hubspot_get_ticket","hubspot_get_users","hubspot_list_appointments","hubspot_list_associations","hubspot_list_carts","hubspot_list_companies","hubspot_list_contacts","hubspot_list_deals","hubspot_list_emails","hubspot_list_line_items","hubspot_list_lists","hubspot_list_marketing_events","hubspot_list_notes","hubspot_list_owners","hubspot_list_quotes","hubspot_list_tickets","hubspot_remove_list_memberships","hubspot_search_companies","hubspot_search_contacts","hubspot_search_deals","hubspot_search_emails","hubspot_search_line_items","hubspot_search_notes","hubspot_search_quotes","hubspot_search_tickets","hubspot_update_appointment","hubspot_update_company","hubspot_update_contact","hubspot_update_deal","hubspot_update_line_item","hubspot_update_ticket","huggingface_chat","hunter_companies_find","hunter_discover","hunter_domain_search","hunter_email_count","hunter_email_finder","hunter_email_verifier","iam_add_user_to_group","iam_attach_role_policy","iam_attach_user_policy","iam_create_access_key","iam_create_role","iam_create_user","iam_delete_access_key","iam_delete_role","iam_delete_user","iam_detach_role_policy","iam_detach_user_policy","iam_get_policy","iam_get_role","iam_get_user","iam_list_access_keys","iam_list_attached_role_policies","iam_list_attached_user_policies","iam_list_groups","iam_list_policies","iam_list_roles","iam_list_users","iam_remove_user_from_group","iam_simulate_principal_policy","iam_update_access_key","icypeas_find_email","icypeas_verify_email","identity_center_check_assignment_deletion_status","identity_center_check_assignment_status","identity_center_create_account_assignment","identity_center_delete_account_assignment","identity_center_describe_account","identity_center_describe_group","identity_center_describe_user","identity_center_get_group","identity_center_get_user","identity_center_list_account_assignments","identity_center_list_accounts","identity_center_list_assignments_for_account","identity_center_list_group_memberships","identity_center_list_groups","identity_center_list_instances","identity_center_list_permission_sets","image_generate","incidentio_actions_create","incidentio_actions_list","incidentio_actions_show","incidentio_actions_update","incidentio_alert_events_create","incidentio_alerts_list","incidentio_alerts_resolve","incidentio_alerts_show","incidentio_catalog_entries_list","incidentio_catalog_types_list","incidentio_custom_fields_create","incidentio_custom_fields_delete","incidentio_custom_fields_list","incidentio_custom_fields_show","incidentio_custom_fields_update","incidentio_escalation_paths_create","incidentio_escalation_paths_delete","incidentio_escalation_paths_list","incidentio_escalation_paths_show","incidentio_escalation_paths_update","incidentio_escalations_cancel","incidentio_escalations_create","incidentio_escalations_list","incidentio_escalations_show","incidentio_follow_ups_create","incidentio_follow_ups_list","incidentio_follow_ups_show","incidentio_follow_ups_update","incidentio_incident_alerts_list","incidentio_incident_memberships_create","incidentio_incident_memberships_revoke","incidentio_incident_participants_list","incidentio_incident_roles_create","incidentio_incident_roles_delete","incidentio_incident_roles_list","incidentio_incident_roles_show","incidentio_incident_roles_update","incidentio_incident_statuses_list","incidentio_incident_timestamps_list","incidentio_incident_timestamps_show","incidentio_incident_types_list","incidentio_incident_updates_list","incidentio_incidents_create","incidentio_incidents_list","incidentio_incidents_show","incidentio_incidents_update","incidentio_on_call_now","incidentio_schedule_entries_list","incidentio_schedule_overrides_create","incidentio_schedule_overrides_list","incidentio_schedules_create","incidentio_schedules_delete","incidentio_schedules_list","incidentio_schedules_show","incidentio_schedules_update","incidentio_severities_list","incidentio_teams_list","incidentio_teams_show","incidentio_users_list","incidentio_users_show","incidentio_workflows_create","incidentio_workflows_delete","incidentio_workflows_list","incidentio_workflows_show","incidentio_workflows_update","infisical_create_secret","infisical_delete_secret","infisical_get_secret","infisical_list_secrets","infisical_update_secret","instagram_delete_comment","instagram_download_media","instagram_get_account_insights","instagram_get_container_status","instagram_get_conversation_messages","instagram_get_media","instagram_get_media_insights","instagram_get_message","instagram_get_profile","instagram_get_publishing_limit","instagram_hide_comment","instagram_list_comments","instagram_list_conversations","instagram_list_media","instagram_list_stories","instagram_private_reply","instagram_publish_carousel","instagram_publish_image","instagram_publish_reel","instagram_publish_story","instagram_publish_video","instagram_reply_to_comment","instagram_send_text_message","instagram_set_comments_enabled","instantly_activate_campaign","instantly_create_campaign","instantly_create_lead","instantly_create_lead_list","instantly_delete_campaign","instantly_delete_leads","instantly_get_lead","instantly_list_campaigns","instantly_list_emails","instantly_list_lead_lists","instantly_list_leads","instantly_patch_campaign","instantly_patch_lead","instantly_pause_campaign","instantly_reply_to_email","instantly_update_lead_interest_status","intercom_assign_conversation_v2","intercom_attach_contact_to_company_v2","intercom_close_conversation_v2","intercom_create_company","intercom_create_company_v2","intercom_create_contact","intercom_create_contact_v2","intercom_create_event_v2","intercom_create_message","intercom_create_message_v2","intercom_create_note_v2","intercom_create_tag_v2","intercom_create_ticket","intercom_create_ticket_v2","intercom_delete_contact","intercom_delete_contact_v2","intercom_detach_contact_from_company_v2","intercom_get_company","intercom_get_company_v2","intercom_get_contact","intercom_get_contact_v2","intercom_get_conversation","intercom_get_conversation_v2","intercom_get_ticket","intercom_get_ticket_v2","intercom_list_admins_v2","intercom_list_companies","intercom_list_companies_v2","intercom_list_contacts","intercom_list_contacts_v2","intercom_list_conversations","intercom_list_conversations_v2","intercom_list_tags_v2","intercom_open_conversation_v2","intercom_reply_conversation","intercom_reply_conversation_v2","intercom_search_contacts","intercom_search_contacts_v2","intercom_search_conversations","intercom_search_conversations_v2","intercom_snooze_conversation_v2","intercom_tag_contact_v2","intercom_tag_conversation_v2","intercom_untag_contact_v2","intercom_update_contact","intercom_update_contact_v2","intercom_update_ticket_v2","jina_read_url","jina_search","jira_add_attachment","jira_add_comment","jira_add_watcher","jira_add_worklog","jira_assign_issue","jira_bulk_read","jira_create_issue_link","jira_delete_attachment","jira_delete_comment","jira_delete_issue","jira_delete_issue_link","jira_delete_worklog","jira_get_attachments","jira_get_comments","jira_get_fields","jira_get_project","jira_get_transitions","jira_get_users","jira_get_worklogs","jira_list_issue_types","jira_list_projects","jira_remove_watcher","jira_retrieve","jira_search_issues","jira_search_users","jira_transition_issue","jira_update","jira_update_comment","jira_update_worklog","jira_write","jotform_add_label_resources","jotform_clone_form","jotform_create_form","jotform_create_label","jotform_create_question","jotform_create_questions","jotform_create_report","jotform_create_submission","jotform_create_submissions","jotform_create_webhook","jotform_delete_form","jotform_delete_label","jotform_delete_question","jotform_delete_report","jotform_delete_submission","jotform_delete_webhook","jotform_get_form","jotform_get_form_properties","jotform_get_history","jotform_get_label","jotform_get_question","jotform_get_report","jotform_get_settings","jotform_get_submission","jotform_get_usage","jotform_get_user","jotform_list_form_files","jotform_list_form_reports","jotform_list_form_submissions","jotform_list_forms","jotform_list_label_resources","jotform_list_labels","jotform_list_questions","jotform_list_reports","jotform_list_submissions","jotform_list_subusers","jotform_list_webhooks","jotform_remove_label_resources","jotform_update_form_properties","jotform_update_label","jotform_update_question","jotform_update_settings","jotform_update_submission","jsm_add_comment","jsm_add_customer","jsm_add_organization","jsm_add_participants","jsm_answer_approval","jsm_attach_form","jsm_copy_forms","jsm_create_object","jsm_create_organization","jsm_create_request","jsm_delete_form","jsm_delete_object","jsm_externalise_form","jsm_get_approvals","jsm_get_comments","jsm_get_customers","jsm_get_form","jsm_get_form_answers","jsm_get_form_structure","jsm_get_form_templates","jsm_get_issue_forms","jsm_get_object","jsm_get_object_schema","jsm_get_object_type_attributes","jsm_get_organizations","jsm_get_participants","jsm_get_queues","jsm_get_request","jsm_get_request_type_fields","jsm_get_request_types","jsm_get_requests","jsm_get_service_desks","jsm_get_sla","jsm_get_transitions","jsm_internalise_form","jsm_list_object_schemas","jsm_list_object_types","jsm_reopen_form","jsm_save_form_answers","jsm_search_objects_aql","jsm_submit_form","jsm_transition_request","jsm_update_object","jupyter_copy_content","jupyter_create_file","jupyter_create_session","jupyter_delete_content","jupyter_delete_session","jupyter_get_content","jupyter_get_content_v2","jupyter_interrupt_kernel","jupyter_list_contents","jupyter_list_kernels","jupyter_list_kernelspecs","jupyter_list_sessions","jupyter_rename_content","jupyter_restart_kernel","jupyter_start_kernel","jupyter_stop_kernel","jupyter_upload_file","kalshi_amend_order","kalshi_amend_order_v2","kalshi_cancel_order","kalshi_cancel_order_v2","kalshi_create_order","kalshi_create_order_v2","kalshi_get_balance","kalshi_get_balance_v2","kalshi_get_candlesticks","kalshi_get_candlesticks_v2","kalshi_get_event","kalshi_get_event_candlesticks","kalshi_get_event_candlesticks_v2","kalshi_get_event_v2","kalshi_get_events","kalshi_get_events_v2","kalshi_get_exchange_announcements","kalshi_get_exchange_announcements_v2","kalshi_get_exchange_schedule","kalshi_get_exchange_schedule_v2","kalshi_get_exchange_status","kalshi_get_exchange_status_v2","kalshi_get_fills","kalshi_get_fills_v2","kalshi_get_market","kalshi_get_market_v2","kalshi_get_markets","kalshi_get_markets_v2","kalshi_get_order","kalshi_get_order_v2","kalshi_get_orderbook","kalshi_get_orderbook_v2","kalshi_get_orders","kalshi_get_orders_v2","kalshi_get_positions","kalshi_get_positions_v2","kalshi_get_series_by_ticker","kalshi_get_series_by_ticker_v2","kalshi_get_series_list","kalshi_get_series_list_v2","kalshi_get_settlements","kalshi_get_settlements_v2","kalshi_get_trades","kalshi_get_trades_v2","ketch_get_consent","ketch_get_subscriptions","ketch_invoke_right","ketch_set_consent","ketch_set_subscriptions","knowledge_create_document","knowledge_delete_chunk","knowledge_delete_document","knowledge_get_connector","knowledge_get_document","knowledge_list_chunks","knowledge_list_connectors","knowledge_list_documents","knowledge_list_tags","knowledge_search","knowledge_trigger_sync","knowledge_update_chunk","knowledge_upload_chunk","knowledge_upsert_document","lambda_add_permission","lambda_create_alias","lambda_create_event_source_mapping","lambda_create_function","lambda_create_function_url_config","lambda_delete_alias","lambda_delete_event_source_mapping","lambda_delete_function","lambda_delete_function_concurrency","lambda_delete_function_event_invoke_config","lambda_delete_function_url_config","lambda_delete_provisioned_concurrency_config","lambda_get_account_settings","lambda_get_alias","lambda_get_event_source_mapping","lambda_get_function","lambda_get_function_concurrency","lambda_get_function_configuration","lambda_get_function_event_invoke_config","lambda_get_function_recursion_config","lambda_get_function_url_config","lambda_get_layer_version","lambda_get_policy","lambda_get_provisioned_concurrency_config","lambda_get_runtime_management_config","lambda_invoke","lambda_list_aliases","lambda_list_event_source_mappings","lambda_list_function_event_invoke_configs","lambda_list_function_url_configs","lambda_list_functions","lambda_list_layer_versions","lambda_list_layers","lambda_list_provisioned_concurrency_configs","lambda_list_tags","lambda_list_versions_by_function","lambda_publish_version","lambda_put_function_concurrency","lambda_put_function_event_invoke_config","lambda_put_function_recursion_config","lambda_put_provisioned_concurrency_config","lambda_put_runtime_management_config","lambda_remove_permission","lambda_tag_resource","lambda_untag_resource","lambda_update_alias","lambda_update_event_source_mapping","lambda_update_function_code","lambda_update_function_configuration","lambda_update_function_url_config","langsmith_create_feedback","langsmith_create_run","langsmith_create_runs_batch","langsmith_get_run","langsmith_update_run","latex_compile","latex_get_package","latex_list_fonts","latex_search_packages","launchdarkly_create_flag","launchdarkly_delete_flag","launchdarkly_get_audit_log","launchdarkly_get_flag","launchdarkly_get_flag_status","launchdarkly_list_environments","launchdarkly_list_flags","launchdarkly_list_members","launchdarkly_list_projects","launchdarkly_list_segments","launchdarkly_toggle_flag","launchdarkly_update_flag","leadmagic_company_search","leadmagic_email_to_profile","leadmagic_find_email","leadmagic_find_mobile","leadmagic_get_credits","leadmagic_profile_search","leadmagic_profile_to_email","leadmagic_role_finder","leadmagic_validate_email","lemlist_get_activities","lemlist_get_lead","lemlist_send_email","linear_add_label_to_issue","linear_add_label_to_project","linear_archive_issue","linear_archive_label","linear_archive_project","linear_create_attachment","linear_create_comment","linear_create_customer","linear_create_customer_request","linear_create_customer_status","linear_create_customer_tier","linear_create_cycle","linear_create_favorite","linear_create_issue","linear_create_issue_relation","linear_create_label","linear_create_project","linear_create_project_label","linear_create_project_milestone","linear_create_project_status","linear_create_project_update","linear_create_workflow_state","linear_delete_attachment","linear_delete_comment","linear_delete_customer","linear_delete_customer_status","linear_delete_customer_tier","linear_delete_issue","linear_delete_issue_relation","linear_delete_project","linear_delete_project_label","linear_delete_project_milestone","linear_delete_project_status","linear_get_active_cycle","linear_get_customer","linear_get_cycle","linear_get_issue","linear_get_project","linear_get_viewer","linear_list_attachments","linear_list_comments","linear_list_customer_requests","linear_list_customer_statuses","linear_list_customer_tiers","linear_list_customers","linear_list_cycles","linear_list_favorites","linear_list_issue_relations","linear_list_labels","linear_list_notifications","linear_list_project_labels","linear_list_project_milestones","linear_list_project_statuses","linear_list_project_updates","linear_list_projects","linear_list_teams","linear_list_users","linear_list_workflow_states","linear_merge_customers","linear_read_issues","linear_remove_label_from_issue","linear_remove_label_from_project","linear_search_issues","linear_unarchive_issue","linear_update_attachment","linear_update_comment","linear_update_customer","linear_update_customer_request","linear_update_customer_status","linear_update_customer_tier","linear_update_issue","linear_update_label","linear_update_notification","linear_update_project","linear_update_project_label","linear_update_project_milestone","linear_update_project_status","linear_update_workflow_state","linkedin_get_profile","linkedin_share_post","linkup_search","linq_add_participant","linq_check_imessage","linq_check_rcs","linq_create_attachment","linq_create_chat","linq_create_contact_card","linq_create_webhook_subscription","linq_delete_attachment","linq_delete_message","linq_delete_webhook_subscription","linq_edit_message","linq_get_attachment","linq_get_chat","linq_get_contact_card","linq_get_message","linq_get_webhook_subscription","linq_leave_chat","linq_list_chats","linq_list_messages","linq_list_phone_numbers","linq_list_thread","linq_list_webhook_events","linq_list_webhook_subscriptions","linq_mark_chat_read","linq_react_to_message","linq_remove_participant","linq_send_message","linq_send_voice_memo","linq_share_contact_card","linq_start_typing","linq_stop_typing","linq_update_chat","linq_update_contact_card","linq_update_webhook_subscription","llm_chat","logfire_get_token_info","logfire_get_trace","logfire_query","logfire_search_records","logrocket_create_release","logrocket_get_audit_logs","logrocket_get_highlights","logrocket_identify_user","logrocket_list_exported_sessions","logrocket_request_highlights","logs_get","logs_get_execution","logs_get_run_details","logs_query","logs_query_runs","loops_check_contact_suppression","loops_create_contact","loops_create_contact_property","loops_delete_contact","loops_find_contact","loops_get_transactional_email","loops_list_contact_properties","loops_list_mailing_lists","loops_list_transactional_emails","loops_remove_contact_suppression","loops_send_event","loops_send_transactional_email","loops_update_contact","luma_add_guests","luma_cancel_event","luma_create_event","luma_get_event","luma_get_guest","luma_get_guests","luma_list_events","luma_lookup_event","luma_send_invites","luma_update_event","luma_update_guest_status","mailchimp_add_member","mailchimp_add_member_tags","mailchimp_add_or_update_member","mailchimp_add_segment_member","mailchimp_add_subscriber_to_automation","mailchimp_archive_member","mailchimp_create_audience","mailchimp_create_batch_operation","mailchimp_create_campaign","mailchimp_create_interest","mailchimp_create_interest_category","mailchimp_create_landing_page","mailchimp_create_merge_field","mailchimp_create_segment","mailchimp_create_template","mailchimp_delete_audience","mailchimp_delete_batch_operation","mailchimp_delete_campaign","mailchimp_delete_interest","mailchimp_delete_interest_category","mailchimp_delete_landing_page","mailchimp_delete_member","mailchimp_delete_merge_field","mailchimp_delete_segment","mailchimp_delete_template","mailchimp_get_audience","mailchimp_get_audiences","mailchimp_get_automation","mailchimp_get_automations","mailchimp_get_batch_operation","mailchimp_get_batch_operations","mailchimp_get_campaign","mailchimp_get_campaign_content","mailchimp_get_campaign_report","mailchimp_get_campaign_reports","mailchimp_get_campaigns","mailchimp_get_interest","mailchimp_get_interest_categories","mailchimp_get_interest_category","mailchimp_get_interests","mailchimp_get_landing_page","mailchimp_get_landing_pages","mailchimp_get_member","mailchimp_get_member_tags","mailchimp_get_members","mailchimp_get_merge_field","mailchimp_get_merge_fields","mailchimp_get_segment","mailchimp_get_segment_members","mailchimp_get_segments","mailchimp_get_template","mailchimp_get_templates","mailchimp_pause_automation","mailchimp_publish_landing_page","mailchimp_remove_member_tags","mailchimp_remove_segment_member","mailchimp_replicate_campaign","mailchimp_schedule_campaign","mailchimp_send_campaign","mailchimp_set_campaign_content","mailchimp_start_automation","mailchimp_unarchive_member","mailchimp_unpublish_landing_page","mailchimp_unschedule_campaign","mailchimp_update_audience","mailchimp_update_campaign","mailchimp_update_interest","mailchimp_update_interest_category","mailchimp_update_landing_page","mailchimp_update_member","mailchimp_update_merge_field","mailchimp_update_segment","mailchimp_update_template","mailgun_add_list_member","mailgun_create_mailing_list","mailgun_get_domain","mailgun_get_mailing_list","mailgun_get_message","mailgun_list_domains","mailgun_list_messages","mailgun_send_message","managed_agent_archive_session","managed_agent_create_session","managed_agent_delete_session","managed_agent_get_session","managed_agent_interrupt_session","managed_agent_list_events","managed_agent_respond_custom_tool","managed_agent_respond_tool_confirmation","managed_agent_run_session","managed_agent_send_message","managed_agent_update_session","manageengine_sdp_add_change_note","manageengine_sdp_add_problem_note","manageengine_sdp_add_request_note","manageengine_sdp_create_asset","manageengine_sdp_create_change","manageengine_sdp_create_problem","manageengine_sdp_create_request","manageengine_sdp_create_solution","manageengine_sdp_delete_asset","manageengine_sdp_delete_change","manageengine_sdp_delete_problem","manageengine_sdp_delete_request","manageengine_sdp_delete_solution","manageengine_sdp_get_asset","manageengine_sdp_get_change","manageengine_sdp_get_problem","manageengine_sdp_get_request","manageengine_sdp_get_solution","manageengine_sdp_list_assets","manageengine_sdp_list_change_notes","manageengine_sdp_list_changes","manageengine_sdp_list_problem_notes","manageengine_sdp_list_problems","manageengine_sdp_list_request_notes","manageengine_sdp_list_requests","manageengine_sdp_list_solutions","manageengine_sdp_update_asset","manageengine_sdp_update_change","manageengine_sdp_update_problem","manageengine_sdp_update_request","manageengine_sdp_update_solution","mcp_list_operations","mcp_run_operation","mem0_add_memories","mem0_get_memories","mem0_search_memories","memory_add","memory_delete","memory_get","memory_get_all","microsoft_ad_add_directory_role_member","microsoft_ad_add_group_member","microsoft_ad_add_user_app_role_assignment","microsoft_ad_assign_license","microsoft_ad_create_group","microsoft_ad_create_user","microsoft_ad_delete_group","microsoft_ad_delete_user","microsoft_ad_get_conditional_access_policy","microsoft_ad_get_device","microsoft_ad_get_group","microsoft_ad_get_user","microsoft_ad_list_authentication_methods","microsoft_ad_list_conditional_access_policies","microsoft_ad_list_devices","microsoft_ad_list_directory_audits","microsoft_ad_list_directory_role_members","microsoft_ad_list_directory_roles","microsoft_ad_list_group_members","microsoft_ad_list_groups","microsoft_ad_list_service_principal_app_role_assignments","microsoft_ad_list_service_principals","microsoft_ad_list_sign_ins","microsoft_ad_list_subscribed_skus","microsoft_ad_list_user_app_role_assignments","microsoft_ad_list_user_devices","microsoft_ad_list_user_licenses","microsoft_ad_list_users","microsoft_ad_remove_directory_role_member","microsoft_ad_remove_group_member","microsoft_ad_remove_user_app_role_assignment","microsoft_ad_reset_password","microsoft_ad_revoke_sign_in_sessions","microsoft_ad_set_password","microsoft_ad_update_group","microsoft_ad_update_user","microsoft_dataverse_associate","microsoft_dataverse_create_multiple","microsoft_dataverse_create_record","microsoft_dataverse_delete_record","microsoft_dataverse_disassociate","microsoft_dataverse_download_file","microsoft_dataverse_download_file_v2","microsoft_dataverse_execute_action","microsoft_dataverse_execute_function","microsoft_dataverse_fetchxml_query","microsoft_dataverse_get_entity_metadata","microsoft_dataverse_get_record","microsoft_dataverse_list_records","microsoft_dataverse_search","microsoft_dataverse_update_multiple","microsoft_dataverse_update_record","microsoft_dataverse_upload_file","microsoft_dataverse_upsert_record","microsoft_dataverse_whoami","microsoft_dynamics_365_close_case","microsoft_dynamics_365_close_opportunity","microsoft_dynamics_365_create_record","microsoft_dynamics_365_get_record","microsoft_dynamics_365_list_records","microsoft_dynamics_365_qualify_lead","microsoft_dynamics_365_search_records","microsoft_dynamics_365_update_record","microsoft_excel_clear_range","microsoft_excel_create_table","microsoft_excel_delete_worksheet","microsoft_excel_format_range","microsoft_excel_read","microsoft_excel_read_v2","microsoft_excel_sort_range","microsoft_excel_table_add","microsoft_excel_worksheet_add","microsoft_excel_write","microsoft_excel_write_v2","microsoft_planner_create_bucket","microsoft_planner_create_plan","microsoft_planner_create_task","microsoft_planner_delete_bucket","microsoft_planner_delete_plan","microsoft_planner_delete_task","microsoft_planner_get_plan_details","microsoft_planner_get_task_details","microsoft_planner_list_buckets","microsoft_planner_list_plans","microsoft_planner_read_bucket","microsoft_planner_read_plan","microsoft_planner_read_task","microsoft_planner_update_bucket","microsoft_planner_update_plan","microsoft_planner_update_plan_details","microsoft_planner_update_task","microsoft_planner_update_task_details","microsoft_teams_delete_channel_message","microsoft_teams_delete_chat_message","microsoft_teams_get_message","microsoft_teams_list_channel_members","microsoft_teams_list_channels","microsoft_teams_list_chat_members","microsoft_teams_list_chats","microsoft_teams_list_team_members","microsoft_teams_list_teams","microsoft_teams_read_channel","microsoft_teams_read_chat","microsoft_teams_reply_to_message","microsoft_teams_set_reaction","microsoft_teams_unset_reaction","microsoft_teams_update_channel_message","microsoft_teams_update_chat_message","microsoft_teams_write_channel","microsoft_teams_write_chat","microsoft_word_append","microsoft_word_create","microsoft_word_create_from_template","microsoft_word_export_pdf","microsoft_word_list","microsoft_word_read","microsoft_word_replace_text","microsoft_word_update","millionverifier_get_credits","millionverifier_verify_email","mintlify_create_agent_job","mintlify_create_assistant_message","mintlify_detect_ai_prose","mintlify_get_agent_job","mintlify_get_assistant_caller_stats","mintlify_get_assistant_conversations","mintlify_get_feedback","mintlify_get_feedback_by_page","mintlify_get_page_content","mintlify_get_searches","mintlify_get_update_status","mintlify_get_views","mintlify_get_visitors","mintlify_search","mintlify_send_agent_message","mintlify_trigger_automation","mintlify_trigger_preview","mintlify_trigger_update","mistral_parser","mistral_parser_v2","mistral_parser_v3","modal_call_function","modal_chat_completion","modal_list_models","monday_archive_item","monday_change_column_value","monday_create_board","monday_create_column","monday_create_group","monday_create_item","monday_create_subitem","monday_create_update","monday_delete_item","monday_duplicate_item","monday_get_board","monday_get_groups","monday_get_item","monday_get_items","monday_list_boards","monday_move_item_to_group","monday_search_items","monday_update_item","mongodb_delete","mongodb_execute","mongodb_insert","mongodb_introspect","mongodb_query","mongodb_update","mssql_delete","mssql_execute","mssql_insert","mssql_introspect","mssql_query","mssql_update","mysql_delete","mysql_execute","mysql_insert","mysql_introspect","mysql_query","mysql_update","neo4j_create","neo4j_delete","neo4j_execute","neo4j_introspect","neo4j_merge","neo4j_query","neo4j_update","netsuite_attach_record","netsuite_batch_create_records","netsuite_batch_delete_records","netsuite_batch_get_records","netsuite_batch_update_records","netsuite_batch_upsert_records","netsuite_create_record","netsuite_delete_record","netsuite_detach_record","netsuite_execute_action","netsuite_execute_dataset","netsuite_execute_suiteql","netsuite_get_async_result","netsuite_get_async_status","netsuite_get_governance_limits","netsuite_get_record","netsuite_get_record_form","netsuite_get_record_metadata","netsuite_get_select_options","netsuite_get_server_time","netsuite_get_subresource","netsuite_list_datasets","netsuite_list_record_types","netsuite_list_records","netsuite_transform_record","netsuite_update_record","netsuite_upsert_record","neverbounce_get_credits","neverbounce_verify_email","new_relic_create_deployment_event","new_relic_get_entity","new_relic_nrql_query","new_relic_search_entities","notion_add_database_row","notion_add_database_row_v2","notion_append_blocks","notion_append_blocks_v2","notion_create_comment","notion_create_comment_v2","notion_create_database","notion_create_database_v2","notion_create_page","notion_create_page_v2","notion_delete_block","notion_delete_block_v2","notion_list_comments","notion_list_comments_v2","notion_list_users","notion_list_users_v2","notion_query_database","notion_query_database_v2","notion_read","notion_read_database","notion_read_database_v2","notion_read_v2","notion_retrieve_block","notion_retrieve_block_children","notion_retrieve_block_children_v2","notion_retrieve_block_v2","notion_retrieve_user","notion_retrieve_user_v2","notion_search","notion_search_v2","notion_update_block","notion_update_block_v2","notion_update_page","notion_update_page_v2","notion_write","notion_write_v2","obsidian_append_active","obsidian_append_note","obsidian_append_periodic_note","obsidian_create_note","obsidian_delete_note","obsidian_execute_command","obsidian_get_active","obsidian_get_note","obsidian_get_periodic_note","obsidian_list_commands","obsidian_list_files","obsidian_open_file","obsidian_patch_active","obsidian_patch_note","obsidian_search","okta_activate_group_rule","okta_activate_user","okta_add_user_to_group","okta_assign_group_to_app","okta_assign_user_role","okta_assign_user_to_app","okta_clear_user_sessions","okta_create_group","okta_create_group_rule","okta_create_user","okta_deactivate_group_rule","okta_deactivate_user","okta_delete_group","okta_delete_group_rule","okta_delete_user","okta_enroll_factor","okta_get_app","okta_get_factor","okta_get_group","okta_get_group_rule","okta_get_logs","okta_get_session","okta_get_user","okta_list_app_groups","okta_list_app_users","okta_list_apps","okta_list_factors","okta_list_group_members","okta_list_group_rules","okta_list_groups","okta_list_user_roles","okta_list_users","okta_remove_group_from_app","okta_remove_user_from_app","okta_remove_user_from_group","okta_remove_user_role","okta_reset_all_factors","okta_reset_factor","okta_reset_password","okta_revoke_session","okta_suspend_user","okta_unsuspend_user","okta_update_group","okta_update_user","onedrive_copy","onedrive_create_folder","onedrive_create_share_link","onedrive_delete","onedrive_download","onedrive_get_drive_info","onedrive_get_item","onedrive_list","onedrive_move","onedrive_search","onedrive_upload","onepassword_create_item","onepassword_delete_item","onepassword_get_item","onepassword_get_item_file","onepassword_get_vault","onepassword_list_items","onepassword_list_vaults","onepassword_replace_item","onepassword_resolve_secret","onepassword_update_item","openai_embeddings","openai_image","outlook_calendar_create_event","outlook_calendar_delete_event","outlook_calendar_get_event","outlook_calendar_list_events","outlook_calendar_respond","outlook_calendar_update_event","outlook_copy","outlook_create_folder","outlook_delete","outlook_draft","outlook_forward","outlook_get_attachment","outlook_list_attachments","outlook_list_folders","outlook_mark_read","outlook_mark_unread","outlook_move","outlook_read","outlook_reply","outlook_reply_all","outlook_search","outlook_send","outlook_update_message","pagerduty_add_note","pagerduty_create_incident","pagerduty_get_incident","pagerduty_get_service","pagerduty_list_escalation_policies","pagerduty_list_incident_alerts","pagerduty_list_incidents","pagerduty_list_oncalls","pagerduty_list_schedules","pagerduty_list_services","pagerduty_list_users","pagerduty_merge_incidents","pagerduty_send_event","pagerduty_snooze_incident","pagerduty_update_incident","parallel_deep_research","parallel_extract","parallel_search","pdl_autocomplete","pdl_bulk_company_enrich","pdl_bulk_person_enrich","pdl_clean_company","pdl_clean_location","pdl_clean_school","pdl_company_enrich","pdl_company_search","pdl_person_enrich","pdl_person_identify","pdl_person_search","perplexity_chat","perplexity_search","persona_approve_inquiry","persona_create_account","persona_create_inquiry","persona_create_report","persona_decline_inquiry","persona_expire_inquiry","persona_generate_inquiry_link","persona_get_account","persona_get_case","persona_get_document","persona_get_inquiry","persona_get_report","persona_get_verification","persona_import_accounts","persona_list_accounts","persona_list_cases","persona_list_inquiries","persona_list_inquiry_templates","persona_list_reports","persona_mark_inquiry_for_review","persona_print_inquiry_pdf","persona_redact_account","persona_redact_inquiry","persona_resume_inquiry","persona_update_account","persona_update_inquiry","pinecone_delete_vectors","pinecone_describe_index","pinecone_describe_index_stats","pinecone_fetch","pinecone_generate_embeddings","pinecone_list_indexes","pinecone_list_vector_ids","pinecone_search_text","pinecone_search_vector","pinecone_update_vector","pinecone_upsert_text","pipedrive_create_activity","pipedrive_create_deal","pipedrive_create_lead","pipedrive_create_project","pipedrive_delete_lead","pipedrive_get_activities","pipedrive_get_all_deals","pipedrive_get_deal","pipedrive_get_files","pipedrive_get_leads","pipedrive_get_mail_messages","pipedrive_get_mail_thread","pipedrive_get_pipeline_deals","pipedrive_get_pipelines","pipedrive_get_projects","pipedrive_update_activity","pipedrive_update_deal","pipedrive_update_lead","pitchbook_company_active_investors","pitchbook_company_bio","pitchbook_company_deal_service_providers","pitchbook_company_deals","pitchbook_company_financials","pitchbook_company_general_service_providers","pitchbook_company_industries","pitchbook_company_investors","pitchbook_company_most_recent_debt_financing","pitchbook_company_most_recent_financials","pitchbook_company_most_recent_financing","pitchbook_company_search","pitchbook_company_similar_companies","pitchbook_company_social_analytics","pitchbook_company_updates","pitchbook_company_vc_exit_predictions","pitchbook_contracts_history","pitchbook_cost_of_calls","pitchbook_credit_history","pitchbook_credit_news","pitchbook_credit_news_bulk","pitchbook_credit_news_most_recent","pitchbook_credit_news_search","pitchbook_deal_bio","pitchbook_deal_cap_table_history","pitchbook_deal_debt_lenders","pitchbook_deal_detailed","pitchbook_deal_investors","pitchbook_deal_multiples","pitchbook_deal_search","pitchbook_deal_service_providers","pitchbook_deal_stock_info","pitchbook_deal_tranche_info","pitchbook_deal_updates","pitchbook_deal_valuation","pitchbook_entity_affiliates","pitchbook_entity_locations","pitchbook_entity_news","pitchbook_entity_people","pitchbook_entity_updates","pitchbook_fund_active_investments","pitchbook_fund_benchmark","pitchbook_fund_bio","pitchbook_fund_cash_flows","pitchbook_fund_commitments","pitchbook_fund_investment_preferences","pitchbook_fund_investments","pitchbook_fund_performance","pitchbook_fund_search","pitchbook_fund_team","pitchbook_fund_updates","pitchbook_investor_active_investments","pitchbook_investor_bio","pitchbook_investor_board_seats","pitchbook_investor_deal_service_providers","pitchbook_investor_funds","pitchbook_investor_general_service_providers","pitchbook_investor_investments","pitchbook_investor_last_closed_fund","pitchbook_investor_preferences","pitchbook_investor_search","pitchbook_investor_updates","pitchbook_limited_partner_actual_allocations","pitchbook_limited_partner_bio","pitchbook_limited_partner_commitment_aggregates","pitchbook_limited_partner_commitment_preferences","pitchbook_limited_partner_commitments_detailed","pitchbook_limited_partner_search","pitchbook_limited_partner_service_providers","pitchbook_limited_partner_target_allocations","pitchbook_limited_partner_updates","pitchbook_lookup_table_structure","pitchbook_lookup_tables","pitchbook_patent_detailed","pitchbook_patent_search","pitchbook_people_search","pitchbook_person_bio","pitchbook_person_contact","pitchbook_person_education_work","pitchbook_sandbox_entities","pitchbook_search","pitchbook_service_provider_bio","pitchbook_service_provider_search","pitchbook_service_provider_updates","pitchbook_serviced_companies","pitchbook_serviced_deals","pitchbook_serviced_funds","pitchbook_serviced_investors","pitchbook_serviced_limited_partners","pitchbook_shared_search","pitchbook_usage_report","polymarket_get_activity","polymarket_get_event","polymarket_get_events","polymarket_get_holders","polymarket_get_last_trade_price","polymarket_get_leaderboard","polymarket_get_market","polymarket_get_markets","polymarket_get_midpoint","polymarket_get_orderbook","polymarket_get_positions","polymarket_get_price","polymarket_get_price_history","polymarket_get_series","polymarket_get_series_by_id","polymarket_get_spread","polymarket_get_tags","polymarket_get_tick_size","polymarket_get_trades","polymarket_search","postgresql_delete","postgresql_execute","postgresql_insert","postgresql_introspect","postgresql_query","postgresql_update","posthog_batch_events","posthog_capture_event","posthog_create_annotation","posthog_create_cohort","posthog_create_dashboard","posthog_create_experiment","posthog_create_feature_flag","posthog_create_insight","posthog_create_survey","posthog_delete_feature_flag","posthog_delete_person","posthog_delete_survey","posthog_evaluate_flags","posthog_get_cohort","posthog_get_dashboard","posthog_get_event_definition","posthog_get_experiment","posthog_get_feature_flag","posthog_get_insight","posthog_get_organization","posthog_get_person","posthog_get_project","posthog_get_property_definition","posthog_get_session_recording","posthog_get_survey","posthog_list_actions","posthog_list_annotations","posthog_list_cohorts","posthog_list_dashboards","posthog_list_event_definitions","posthog_list_experiments","posthog_list_feature_flags","posthog_list_insights","posthog_list_organizations","posthog_list_persons","posthog_list_projects","posthog_list_property_definitions","posthog_list_recording_playlists","posthog_list_session_recordings","posthog_list_surveys","posthog_query","posthog_update_cohort","posthog_update_event_definition","posthog_update_experiment","posthog_update_feature_flag","posthog_update_insight","posthog_update_property_definition","posthog_update_survey","profound_bot_logs","profound_bots_report","profound_category_assets","profound_category_personas","profound_category_prompts","profound_category_tags","profound_category_topics","profound_citation_prompts","profound_citations_report","profound_list_assets","profound_list_categories","profound_list_domains","profound_list_models","profound_list_optimizations","profound_list_personas","profound_list_regions","profound_optimization_analysis","profound_prompt_answers","profound_prompt_volume","profound_query_fanouts","profound_raw_logs","profound_referrals_report","profound_sentiment_report","profound_visibility_report","prospeo_account_information","prospeo_bulk_enrich_company","prospeo_bulk_enrich_person","prospeo_enrich_company","prospeo_enrich_person","prospeo_search_company","prospeo_search_person","prospeo_search_suggestions","pulse_parser","pulse_parser_v2","qdrant_fetch_points","qdrant_search_vector","qdrant_upsert_points","quartr_get_audio","quartr_get_company","quartr_get_event","quartr_get_event_summary","quartr_get_report","quartr_get_slide_deck","quartr_get_transcript","quartr_list_audio","quartr_list_companies","quartr_list_document_types","quartr_list_documents","quartr_list_event_types","quartr_list_events","quartr_list_live_events","quartr_list_reports","quartr_list_slide_decks","quartr_list_transcripts","quickbooks_add_attachment","quickbooks_create_bill","quickbooks_create_bill_payment","quickbooks_create_credit_memo","quickbooks_create_customer","quickbooks_create_customer_payment","quickbooks_create_deposit","quickbooks_create_employee","quickbooks_create_estimate","quickbooks_create_invoice","quickbooks_create_item","quickbooks_create_journal_entry","quickbooks_create_purchase","quickbooks_create_purchase_order","quickbooks_create_refund_receipt","quickbooks_create_sales_receipt","quickbooks_create_vendor","quickbooks_create_vendor_credit","quickbooks_download_attachment","quickbooks_download_transaction_pdf","quickbooks_email_transaction","quickbooks_get_company_info","quickbooks_read_accounting_transactions","quickbooks_read_attachments","quickbooks_read_master_data","quickbooks_read_purchasing_transactions","quickbooks_read_sales_transactions","quickbooks_run_financial_report","quickbooks_update_bill","quickbooks_update_bill_payment","quickbooks_update_credit_memo","quickbooks_update_customer","quickbooks_update_customer_payment","quickbooks_update_deposit","quickbooks_update_employee","quickbooks_update_estimate","quickbooks_update_invoice","quickbooks_update_item","quickbooks_update_journal_entry","quickbooks_update_purchase","quickbooks_update_purchase_order","quickbooks_update_refund_receipt","quickbooks_update_sales_receipt","quickbooks_update_vendor","quickbooks_update_vendor_credit","quickbooks_void_bill_payment","quickbooks_void_customer_payment","quickbooks_void_invoice","quickbooks_void_sales_receipt","quiver_image_to_svg","quiver_image_to_svg_v2","quiver_list_models","quiver_text_to_svg","quiver_text_to_svg_v2","rabbitmq_create_binding","rabbitmq_create_exchange","rabbitmq_create_policy","rabbitmq_create_queue","rabbitmq_delete_binding","rabbitmq_delete_exchange","rabbitmq_delete_policy","rabbitmq_delete_queue","rabbitmq_get_exchange","rabbitmq_get_messages","rabbitmq_get_overview","rabbitmq_get_queue","rabbitmq_health_check","rabbitmq_list_bindings","rabbitmq_list_channels","rabbitmq_list_connections","rabbitmq_list_consumers","rabbitmq_list_exchange_bindings","rabbitmq_list_exchanges","rabbitmq_list_nodes","rabbitmq_list_policies","rabbitmq_list_queues","rabbitmq_list_vhosts","rabbitmq_publish_message","rabbitmq_purge_queue","railway_create_environment","railway_create_project","railway_create_service","railway_delete_environment","railway_delete_project","railway_delete_service","railway_delete_variable","railway_deploy_service","railway_get_deployment","railway_get_deployment_logs","railway_get_project","railway_list_deployments","railway_list_project_members","railway_list_projects","railway_list_variables","railway_restart_deployment","railway_rollback_deployment","railway_transfer_project","railway_update_project","railway_upsert_variable","rb2b_credit_check","rb2b_email_to_activity","rb2b_hem_to_best_linkedin","rb2b_hem_to_business_profile","rb2b_hem_to_linkedin","rb2b_hem_to_maid","rb2b_ip_to_company","rb2b_ip_to_hem","rb2b_ip_to_maid","rb2b_linkedin_slug_search","rb2b_linkedin_to_best_personal_email","rb2b_linkedin_to_business_profile","rb2b_linkedin_to_hashed_emails","rb2b_linkedin_to_mobile_phone","rb2b_linkedin_to_personal_email","rds_delete","rds_execute","rds_insert","rds_introspect","rds_query","rds_update","reddit_delete","reddit_edit","reddit_get_comments","reddit_get_controversial","reddit_get_info","reddit_get_me","reddit_get_messages","reddit_get_posts","reddit_get_saved","reddit_get_subreddit_info","reddit_get_subreddit_rules","reddit_get_user","reddit_get_user_comments","reddit_get_user_posts","reddit_hide","reddit_hot_posts","reddit_list_my_subreddits","reddit_lock","reddit_mark_all_read","reddit_mark_read","reddit_marknsfw","reddit_mod_approve","reddit_mod_distinguish","reddit_mod_remove","reddit_mod_sticky","reddit_reply","reddit_report","reddit_save","reddit_search","reddit_search_subreddits","reddit_send_message","reddit_submit_post","reddit_subscribe","reddit_unhide","reddit_unlock","reddit_unmarknsfw","reddit_unsave","reddit_vote","redis_command","redis_delete","redis_exists","redis_expire","redis_get","redis_hdel","redis_hget","redis_hgetall","redis_hset","redis_incr","redis_incrby","redis_keys","redis_llen","redis_lpop","redis_lpush","redis_lrange","redis_persist","redis_rpop","redis_rpush","redis_set","redis_setnx","redis_ttl","reducto_parser","reducto_parser_v2","resend_cancel_email","resend_create_audience","resend_create_broadcast","resend_create_contact","resend_delete_audience","resend_delete_contact","resend_get_audience","resend_get_broadcast","resend_get_contact","resend_get_email","resend_list_audiences","resend_list_contacts","resend_list_domains","resend_send","resend_send_broadcast","resend_update_contact","revenuecat_create_purchase","revenuecat_defer_google_subscription","revenuecat_delete_customer","revenuecat_get_customer","revenuecat_grant_entitlement","revenuecat_list_offerings","revenuecat_refund_google_subscription","revenuecat_revoke_entitlement","revenuecat_revoke_google_subscription","revenuecat_update_subscriber_attributes","rippling_bulk_create_custom_object_records","rippling_bulk_delete_custom_object_records","rippling_bulk_update_custom_object_records","rippling_create_business_partner","rippling_create_business_partner_group","rippling_create_custom_app","rippling_create_custom_object","rippling_create_custom_object_field","rippling_create_custom_object_record","rippling_create_custom_page","rippling_create_custom_setting","rippling_create_department","rippling_create_draft_hires","rippling_create_object_category","rippling_create_title","rippling_create_work_location","rippling_delete_business_partner","rippling_delete_business_partner_group","rippling_delete_custom_app","rippling_delete_custom_object","rippling_delete_custom_object_field","rippling_delete_custom_object_record","rippling_delete_custom_page","rippling_delete_custom_setting","rippling_delete_object_category","rippling_delete_title","rippling_delete_work_location","rippling_get_business_partner","rippling_get_business_partner_group","rippling_get_current_user","rippling_get_custom_app","rippling_get_custom_object","rippling_get_custom_object_field","rippling_get_custom_object_record","rippling_get_custom_object_record_by_external_id","rippling_get_custom_page","rippling_get_custom_setting","rippling_get_department","rippling_get_employment_type","rippling_get_job_function","rippling_get_object_category","rippling_get_report_run","rippling_get_supergroup","rippling_get_team","rippling_get_title","rippling_get_user","rippling_get_work_location","rippling_get_worker","rippling_list_business_partner_groups","rippling_list_business_partners","rippling_list_companies","rippling_list_custom_apps","rippling_list_custom_fields","rippling_list_custom_object_fields","rippling_list_custom_object_records","rippling_list_custom_objects","rippling_list_custom_pages","rippling_list_custom_settings","rippling_list_departments","rippling_list_employment_types","rippling_list_entitlements","rippling_list_job_functions","rippling_list_object_categories","rippling_list_supergroup_exclusion_members","rippling_list_supergroup_inclusion_members","rippling_list_supergroup_members","rippling_list_supergroups","rippling_list_teams","rippling_list_titles","rippling_list_users","rippling_list_work_locations","rippling_list_workers","rippling_query_custom_object_records","rippling_trigger_report_run","rippling_update_custom_app","rippling_update_custom_object","rippling_update_custom_object_field","rippling_update_custom_object_record","rippling_update_custom_page","rippling_update_custom_setting","rippling_update_department","rippling_update_object_category","rippling_update_supergroup_exclusion_members","rippling_update_supergroup_inclusion_members","rippling_update_title","rippling_update_work_location","rocketlane_add_field_option","rocketlane_add_project_members","rocketlane_add_task_assignees","rocketlane_add_task_dependencies","rocketlane_add_task_followers","rocketlane_archive_project","rocketlane_assign_placeholders","rocketlane_create_field","rocketlane_create_phase","rocketlane_create_project","rocketlane_create_space","rocketlane_create_space_document","rocketlane_create_task","rocketlane_create_time_entry","rocketlane_create_time_off","rocketlane_delete_field","rocketlane_delete_phase","rocketlane_delete_project","rocketlane_delete_space","rocketlane_delete_space_document","rocketlane_delete_task","rocketlane_delete_time_entry","rocketlane_delete_time_off","rocketlane_get_field","rocketlane_get_invoice","rocketlane_get_invoice_line_items","rocketlane_get_invoice_payments","rocketlane_get_phase","rocketlane_get_project","rocketlane_get_space","rocketlane_get_space_document","rocketlane_get_task","rocketlane_get_time_entry","rocketlane_get_time_off","rocketlane_get_user","rocketlane_import_template","rocketlane_list_fields","rocketlane_list_invoices","rocketlane_list_phases","rocketlane_list_placeholders","rocketlane_list_projects","rocketlane_list_resource_allocations","rocketlane_list_space_documents","rocketlane_list_spaces","rocketlane_list_tasks","rocketlane_list_time_entries","rocketlane_list_time_entry_categories","rocketlane_list_time_offs","rocketlane_list_users","rocketlane_move_task_to_phase","rocketlane_remove_project_members","rocketlane_remove_task_assignees","rocketlane_remove_task_dependencies","rocketlane_remove_task_followers","rocketlane_search_time_entries","rocketlane_unassign_placeholders","rocketlane_update_field","rocketlane_update_field_option","rocketlane_update_phase","rocketlane_update_project","rocketlane_update_space","rocketlane_update_space_document","rocketlane_update_task","rocketlane_update_time_entry","rootly_acknowledge_alert","rootly_add_incident_event","rootly_add_subscribers","rootly_assign_incident_role","rootly_create_action_item","rootly_create_alert","rootly_create_incident","rootly_create_status_page_event","rootly_delete_action_item","rootly_delete_incident","rootly_escalate_alert","rootly_get_alert","rootly_get_incident","rootly_list_action_items","rootly_list_alerts","rootly_list_causes","rootly_list_environments","rootly_list_escalation_policies","rootly_list_functionalities","rootly_list_incident_events","rootly_list_incident_roles","rootly_list_incident_types","rootly_list_incidents","rootly_list_on_calls","rootly_list_playbooks","rootly_list_retrospectives","rootly_list_schedules","rootly_list_services","rootly_list_severities","rootly_list_teams","rootly_list_users","rootly_mitigate_incident","rootly_remove_subscribers","rootly_resolve_alert","rootly_resolve_incident","rootly_run_workflow","rootly_snooze_alert","rootly_unassign_incident_role","rootly_update_action_item","rootly_update_alert","rootly_update_incident","s3_copy_object","s3_create_bucket","s3_delete_bucket","s3_delete_object","s3_delete_objects","s3_get_object","s3_head_object","s3_list_buckets","s3_list_objects","s3_presigned_url","s3_put_object","sailpoint_approve_access_request","sailpoint_cancel_access_request","sailpoint_decide_certification_review_items","sailpoint_get_access_profile","sailpoint_get_access_profile_entitlements","sailpoint_get_access_request_config","sailpoint_get_access_request_status","sailpoint_get_account","sailpoint_get_account_activity","sailpoint_get_account_entitlements","sailpoint_get_account_selections","sailpoint_get_campaign","sailpoint_get_certification","sailpoint_get_entitlement","sailpoint_get_entitlement_request_config","sailpoint_get_identity","sailpoint_get_role","sailpoint_get_role_entitlements","sailpoint_get_source","sailpoint_get_task_status","sailpoint_list_access_profiles","sailpoint_list_account_activities","sailpoint_list_accounts","sailpoint_list_campaigns","sailpoint_list_certification_review_items","sailpoint_list_certifications","sailpoint_list_entitlements","sailpoint_list_identities","sailpoint_list_identity_entitlements","sailpoint_list_pending_access_request_approvals","sailpoint_list_roles","sailpoint_list_sources","sailpoint_load_accounts","sailpoint_load_entitlements","sailpoint_reject_access_request","sailpoint_request_access","sailpoint_search","sailpoint_search_aggregate","sailpoint_search_count","sailpoint_sign_off_certification","salesforce_create_account","salesforce_create_case","salesforce_create_contact","salesforce_create_custom_field","salesforce_create_custom_object","salesforce_create_lead","salesforce_create_opportunity","salesforce_create_task","salesforce_delete_account","salesforce_delete_case","salesforce_delete_contact","salesforce_delete_custom_field","salesforce_delete_lead","salesforce_delete_opportunity","salesforce_delete_task","salesforce_describe_object","salesforce_get_accounts","salesforce_get_cases","salesforce_get_contacts","salesforce_get_dashboard","salesforce_get_leads","salesforce_get_opportunities","salesforce_get_report","salesforce_get_tasks","salesforce_list_dashboards","salesforce_list_objects","salesforce_list_report_types","salesforce_list_reports","salesforce_query","salesforce_query_more","salesforce_refresh_dashboard","salesforce_run_report","salesforce_tooling_query","salesforce_update_account","salesforce_update_case","salesforce_update_contact","salesforce_update_custom_field","salesforce_update_lead","salesforce_update_opportunity","salesforce_update_task","sap_concur_approve_expense_report","sap_concur_associate_attendees","sap_concur_create_cash_advance","sap_concur_create_expected_expense","sap_concur_create_expense_report","sap_concur_create_list_item","sap_concur_create_purchase_request","sap_concur_create_quick_expense","sap_concur_create_quick_expense_with_image","sap_concur_create_report_comment","sap_concur_create_travel_request","sap_concur_create_user","sap_concur_delete_expected_expense","sap_concur_delete_expense","sap_concur_delete_expense_report","sap_concur_delete_list_item","sap_concur_delete_travel_request","sap_concur_delete_user","sap_concur_get_allocation","sap_concur_get_budget","sap_concur_get_cash_advance","sap_concur_get_expected_expense","sap_concur_get_expense","sap_concur_get_expense_report","sap_concur_get_itemizations","sap_concur_get_itinerary","sap_concur_get_list","sap_concur_get_list_item","sap_concur_get_purchase_request","sap_concur_get_receipt","sap_concur_get_receipt_status","sap_concur_get_request_cash_advance","sap_concur_get_travel_profile","sap_concur_get_travel_request","sap_concur_get_user","sap_concur_issue_cash_advance","sap_concur_list_allocations","sap_concur_list_attendee_associations","sap_concur_list_budget_categories","sap_concur_list_budgets","sap_concur_list_exceptions","sap_concur_list_expected_expenses","sap_concur_list_expense_reports","sap_concur_list_expenses","sap_concur_list_itineraries","sap_concur_list_list_items","sap_concur_list_lists","sap_concur_list_receipts","sap_concur_list_report_comments","sap_concur_list_reports_to_approve","sap_concur_list_travel_profiles_summary","sap_concur_list_travel_request_comments","sap_concur_list_travel_requests","sap_concur_list_users","sap_concur_move_travel_request","sap_concur_recall_expense_report","sap_concur_remove_all_attendees","sap_concur_search_locations","sap_concur_search_users","sap_concur_send_back_expense_report","sap_concur_submit_expense_report","sap_concur_update_allocation","sap_concur_update_expected_expense","sap_concur_update_expense","sap_concur_update_expense_report","sap_concur_update_list_item","sap_concur_update_travel_request","sap_concur_update_user","sap_concur_upload_exchange_rates","sap_concur_upload_receipt_image","sap_s4hana_create_business_partner","sap_s4hana_create_purchase_order","sap_s4hana_create_purchase_requisition","sap_s4hana_create_sales_order","sap_s4hana_delete_sales_order","sap_s4hana_get_billing_document","sap_s4hana_get_business_partner","sap_s4hana_get_customer","sap_s4hana_get_inbound_delivery","sap_s4hana_get_material_document","sap_s4hana_get_outbound_delivery","sap_s4hana_get_product","sap_s4hana_get_purchase_order","sap_s4hana_get_purchase_requisition","sap_s4hana_get_sales_order","sap_s4hana_get_supplier","sap_s4hana_get_supplier_invoice","sap_s4hana_list_billing_documents","sap_s4hana_list_business_partners","sap_s4hana_list_customers","sap_s4hana_list_inbound_deliveries","sap_s4hana_list_material_documents","sap_s4hana_list_material_stock","sap_s4hana_list_outbound_deliveries","sap_s4hana_list_products","sap_s4hana_list_purchase_orders","sap_s4hana_list_purchase_requisitions","sap_s4hana_list_sales_orders","sap_s4hana_list_supplier_invoices","sap_s4hana_list_suppliers","sap_s4hana_odata_query","sap_s4hana_update_business_partner","sap_s4hana_update_customer","sap_s4hana_update_product","sap_s4hana_update_purchase_order","sap_s4hana_update_purchase_requisition","sap_s4hana_update_sales_order","sap_s4hana_update_supplier","search_tool","secrets_manager_create_secret","secrets_manager_delete_secret","secrets_manager_describe_secret","secrets_manager_get_secret","secrets_manager_list_secrets","secrets_manager_restore_secret","secrets_manager_rotate_secret","secrets_manager_tag_resource","secrets_manager_untag_resource","secrets_manager_update_secret","semrush_backlinks","semrush_backlinks_anchors","semrush_backlinks_competitors","semrush_backlinks_geo_distribution","semrush_backlinks_indexed_pages","semrush_backlinks_overview","semrush_backlinks_tld_distribution","semrush_batch_keyword_overview","semrush_broad_match_keywords","semrush_domain_ad_copies","semrush_domain_ad_history","semrush_domain_organic_competitors","semrush_domain_organic_keywords","semrush_domain_overview","semrush_domain_overview_all","semrush_domain_overview_history","semrush_domain_paid_competitors","semrush_domain_paid_keywords","semrush_domain_pla_copies","semrush_domain_pla_keywords","semrush_domain_vs_domain","semrush_keyword_ad_history","semrush_keyword_difficulty","semrush_keyword_overview","semrush_keyword_overview_all","semrush_keyword_questions","semrush_organic_results","semrush_paid_results","semrush_referring_domains","semrush_referring_ips","semrush_related_keywords","semrush_subdomain_ad_copies","semrush_subdomain_organic_keywords","semrush_subdomain_overview","semrush_subdomain_overview_all","semrush_subdomain_overview_history","semrush_subdomain_paid_keywords","semrush_top_domains","semrush_url_organic_keywords","semrush_url_overview","semrush_url_overview_all","semrush_url_overview_history","semrush_url_paid_keywords","semrush_winners_and_losers","sendblue_evaluate_service","sendblue_get_message","sendblue_send_group_message","sendblue_send_message","sendblue_send_typing_indicator","sendgrid_add_contact","sendgrid_add_contacts_to_list","sendgrid_create_list","sendgrid_create_template","sendgrid_create_template_version","sendgrid_delete_contacts","sendgrid_delete_list","sendgrid_delete_template","sendgrid_get_contact","sendgrid_get_list","sendgrid_get_template","sendgrid_list_all_lists","sendgrid_list_templates","sendgrid_remove_contacts_from_list","sendgrid_search_contacts","sendgrid_send_mail","sentry_events_get","sentry_events_list","sentry_issues_get","sentry_issues_list","sentry_issues_update","sentry_projects_create","sentry_projects_get","sentry_projects_list","sentry_projects_update","sentry_releases_create","sentry_releases_deploy","sentry_releases_list","sentry_teams_list","serper_search","servicenow_add_incident_comment","servicenow_aggregate","servicenow_close_incident","servicenow_create_change_request","servicenow_create_incident","servicenow_create_record","servicenow_delete_record","servicenow_download_attachment","servicenow_download_attachment_v2","servicenow_find_user","servicenow_get_change_next_states","servicenow_get_change_request","servicenow_get_ci","servicenow_get_incident","servicenow_get_knowledge_article","servicenow_get_requested_item","servicenow_list_approvals","servicenow_list_attachments","servicenow_list_catalog_items","servicenow_list_change_requests","servicenow_list_change_tasks","servicenow_list_ci_relationships","servicenow_list_group_members","servicenow_list_incidents","servicenow_list_requested_items","servicenow_order_catalog_item","servicenow_read_record","servicenow_resolve_incident","servicenow_search_cis","servicenow_search_knowledge","servicenow_update_approval","servicenow_update_change_request","servicenow_update_change_state","servicenow_update_incident","servicenow_update_record","servicenow_upload_attachment","ses_create_configuration_set","ses_create_email_identity","ses_create_template","ses_delete_email_identity","ses_delete_suppressed_destination","ses_delete_template","ses_get_account","ses_get_email_identity","ses_get_suppressed_destination","ses_get_template","ses_list_identities","ses_list_suppressed_destinations","ses_list_templates","ses_put_suppressed_destination","ses_send_bulk_email","ses_send_custom_verification_email","ses_send_email","ses_send_templated_email","ses_update_template","sftp_delete","sftp_download","sftp_download_v2","sftp_list","sftp_mkdir","sftp_upload","sharepoint_add_list_items","sharepoint_create_list","sharepoint_create_page","sharepoint_delete_file","sharepoint_delete_list_item","sharepoint_delete_page","sharepoint_download_file","sharepoint_get_drive_item","sharepoint_get_list","sharepoint_get_list_item","sharepoint_list_sites","sharepoint_publish_page","sharepoint_read_page","sharepoint_update_list","sharepoint_update_page","sharepoint_upload_file","shopify_adjust_inventory","shopify_cancel_order","shopify_create_customer","shopify_create_fulfillment","shopify_create_product","shopify_delete_customer","shopify_delete_product","shopify_get_collection","shopify_get_customer","shopify_get_inventory_level","shopify_get_order","shopify_get_product","shopify_list_collections","shopify_list_customers","shopify_list_inventory_items","shopify_list_locations","shopify_list_orders","shopify_list_products","shopify_update_customer","shopify_update_order","shopify_update_product","similarweb_bounce_rate","similarweb_page_views","similarweb_pages_per_visit","similarweb_traffic_visits","similarweb_visit_duration","similarweb_website_overview","sixtyfour_enrich_company","sixtyfour_enrich_lead","sixtyfour_find_email","sixtyfour_find_phone","slack_add_reaction","slack_archive_conversation","slack_canvas","slack_create_channel_canvas","slack_create_conversation","slack_delete_canvas","slack_delete_message","slack_delete_scheduled_message","slack_download","slack_edit_canvas","slack_ephemeral_message","slack_get_canvas","slack_get_channel_history","slack_get_channel_info","slack_get_message","slack_get_permalink","slack_get_thread","slack_get_thread_replies","slack_get_user","slack_get_user_presence","slack_invite_to_conversation","slack_list_canvases","slack_list_channels","slack_list_members","slack_list_scheduled_messages","slack_list_users","slack_lookup_canvas_sections","slack_message","slack_message_reader","slack_open_view","slack_publish_view","slack_push_view","slack_remove_reaction","slack_rename_agent_session_v2","slack_rename_conversation","slack_schedule_message","slack_set_agent_session_status_v2","slack_set_conversation_purpose","slack_set_conversation_topic","slack_set_status","slack_set_suggested_prompts","slack_set_suggested_prompts_v2","slack_set_title","slack_update_message","slack_update_view","smartlead_add_email_accounts_to_campaign","smartlead_add_leads_to_campaign","smartlead_create_campaign","smartlead_create_lead_list","smartlead_delete_campaign","smartlead_delete_campaign_webhook","smartlead_delete_lead_from_campaign","smartlead_delete_lead_list","smartlead_duplicate_campaign","smartlead_export_campaign_leads","smartlead_get_campaign","smartlead_get_campaign_analytics","smartlead_get_campaign_analytics_by_date","smartlead_get_campaign_lead_statistics","smartlead_get_campaign_mailbox_statistics","smartlead_get_campaign_sequences","smartlead_get_campaign_statistics","smartlead_get_campaign_top_level_analytics_by_date","smartlead_get_campaign_webhook_summary","smartlead_get_lead_by_email","smartlead_get_lead_by_id","smartlead_get_lead_list","smartlead_get_lead_message_history","smartlead_list_campaign_email_accounts","smartlead_list_campaign_leads","smartlead_list_campaign_webhooks","smartlead_list_campaigns","smartlead_list_clients","smartlead_list_email_accounts","smartlead_list_inbox_replies","smartlead_list_lead_activities","smartlead_list_lead_categories","smartlead_list_lead_lists","smartlead_mark_lead_complete","smartlead_pause_lead","smartlead_remove_email_accounts_from_campaign","smartlead_resume_lead","smartlead_save_campaign_sequences","smartlead_unsubscribe_lead_from_campaign","smartlead_unsubscribe_lead_globally","smartlead_update_campaign_schedule","smartlead_update_campaign_settings","smartlead_update_campaign_status","smartlead_update_lead","smartlead_update_lead_category","smartlead_update_lead_list","smartlead_upsert_campaign_webhook","sms_send","smtp_send_mail","snowflake_alter_warehouse","snowflake_call_procedure","snowflake_cancel_statement","snowflake_cancel_task_run","snowflake_delete_rows","snowflake_execute_sql","snowflake_get_statement","snowflake_get_task","snowflake_get_task_run","snowflake_get_task_run_output","snowflake_get_warehouse","snowflake_insert_rows","snowflake_introspect_schema","snowflake_list_copy_history","snowflake_list_databases","snowflake_list_query_history","snowflake_list_schemas","snowflake_list_tables","snowflake_list_task_runs","snowflake_list_tasks","snowflake_list_warehouses","snowflake_load_data","snowflake_resume_task","snowflake_resume_warehouse","snowflake_run_task","snowflake_suspend_task","snowflake_suspend_warehouse","snowflake_unload_data","snowflake_update_rows","snowflake_upsert_rows","splunk_cancel_search_job","splunk_create_search_job","splunk_dispatch_saved_search","splunk_get_fired_alerts","splunk_get_saved_search","splunk_get_search_job","splunk_get_search_results","splunk_list_apps","splunk_list_fired_alerts","splunk_list_indexes","splunk_list_saved_searches","splunk_run_search","sportmonks_core_get_cities","sportmonks_core_get_city","sportmonks_core_get_continent","sportmonks_core_get_continents","sportmonks_core_get_countries","sportmonks_core_get_country","sportmonks_core_get_entity_filters","sportmonks_core_get_my_usage","sportmonks_core_get_region","sportmonks_core_get_regions","sportmonks_core_get_timezones","sportmonks_core_get_type","sportmonks_core_get_type_by_entity","sportmonks_core_get_types","sportmonks_core_search_cities","sportmonks_core_search_countries","sportmonks_core_search_regions","sportmonks_football_expected_by_player","sportmonks_football_expected_by_team","sportmonks_football_get_all_commentaries","sportmonks_football_get_all_fixtures","sportmonks_football_get_all_players","sportmonks_football_get_all_rivals","sportmonks_football_get_all_teams","sportmonks_football_get_all_transfer_rumours","sportmonks_football_get_all_transfers","sportmonks_football_get_brackets_by_season","sportmonks_football_get_coach","sportmonks_football_get_coaches","sportmonks_football_get_coaches_by_country","sportmonks_football_get_commentaries_by_fixture","sportmonks_football_get_current_leagues_by_team","sportmonks_football_get_expected_lineups_by_player","sportmonks_football_get_expected_lineups_by_team","sportmonks_football_get_extended_team_squad","sportmonks_football_get_fixture","sportmonks_football_get_fixtures_by_date","sportmonks_football_get_fixtures_by_date_range","sportmonks_football_get_fixtures_by_date_range_for_team","sportmonks_football_get_fixtures_by_ids","sportmonks_football_get_grouped_standings_by_round","sportmonks_football_get_head_to_head","sportmonks_football_get_inplay_livescores","sportmonks_football_get_latest_coaches","sportmonks_football_get_latest_fixtures","sportmonks_football_get_latest_livescores","sportmonks_football_get_latest_players","sportmonks_football_get_latest_totw","sportmonks_football_get_latest_transfers","sportmonks_football_get_league","sportmonks_football_get_leagues","sportmonks_football_get_leagues_by_country","sportmonks_football_get_leagues_by_date","sportmonks_football_get_leagues_by_team","sportmonks_football_get_live_leagues","sportmonks_football_get_live_probabilities","sportmonks_football_get_live_probabilities_by_fixture","sportmonks_football_get_live_standings_by_league","sportmonks_football_get_livescores","sportmonks_football_get_match_facts","sportmonks_football_get_match_facts_by_date_range","sportmonks_football_get_match_facts_by_fixture","sportmonks_football_get_match_facts_by_league","sportmonks_football_get_past_fixtures_by_tv_station","sportmonks_football_get_player","sportmonks_football_get_players_by_country","sportmonks_football_get_postmatch_news","sportmonks_football_get_postmatch_news_by_season","sportmonks_football_get_predictability_by_league","sportmonks_football_get_prematch_news","sportmonks_football_get_prematch_news_by_season","sportmonks_football_get_prematch_news_upcoming","sportmonks_football_get_probabilities","sportmonks_football_get_probabilities_by_fixture","sportmonks_football_get_referee","sportmonks_football_get_referees","sportmonks_football_get_referees_by_country","sportmonks_football_get_referees_by_season","sportmonks_football_get_rivals_by_team","sportmonks_football_get_round","sportmonks_football_get_round_statistics","sportmonks_football_get_rounds","sportmonks_football_get_rounds_by_season","sportmonks_football_get_schedules_by_season","sportmonks_football_get_schedules_by_season_and_team","sportmonks_football_get_schedules_by_team","sportmonks_football_get_season","sportmonks_football_get_seasons","sportmonks_football_get_seasons_by_team","sportmonks_football_get_stage","sportmonks_football_get_stage_statistics","sportmonks_football_get_stages","sportmonks_football_get_stages_by_season","sportmonks_football_get_standing_corrections_by_season","sportmonks_football_get_standings","sportmonks_football_get_standings_by_round","sportmonks_football_get_standings_by_season","sportmonks_football_get_state","sportmonks_football_get_states","sportmonks_football_get_team","sportmonks_football_get_team_rankings","sportmonks_football_get_team_rankings_by_date","sportmonks_football_get_team_rankings_by_team","sportmonks_football_get_team_squad","sportmonks_football_get_team_squad_by_season","sportmonks_football_get_teams_by_country","sportmonks_football_get_teams_by_season","sportmonks_football_get_topscorers_by_season","sportmonks_football_get_topscorers_by_stage","sportmonks_football_get_totw","sportmonks_football_get_totw_by_round","sportmonks_football_get_transfer","sportmonks_football_get_transfer_rumour","sportmonks_football_get_transfer_rumours_between_dates","sportmonks_football_get_transfer_rumours_by_player","sportmonks_football_get_transfer_rumours_by_team","sportmonks_football_get_transfers_between_dates","sportmonks_football_get_transfers_by_player","sportmonks_football_get_transfers_by_team","sportmonks_football_get_tv_station","sportmonks_football_get_tv_stations","sportmonks_football_get_tv_stations_by_fixture","sportmonks_football_get_upcoming_fixtures_by_market","sportmonks_football_get_upcoming_fixtures_by_tv_station","sportmonks_football_get_value_bets","sportmonks_football_get_value_bets_by_fixture","sportmonks_football_get_venue","sportmonks_football_get_venues","sportmonks_football_get_venues_by_season","sportmonks_football_search_coaches","sportmonks_football_search_fixtures","sportmonks_football_search_leagues","sportmonks_football_search_players","sportmonks_football_search_referees","sportmonks_football_search_rounds","sportmonks_football_search_seasons","sportmonks_football_search_stages","sportmonks_football_search_teams","sportmonks_football_search_venues","sportmonks_motorsport_get_all_fixtures","sportmonks_motorsport_get_current_leagues_by_team","sportmonks_motorsport_get_driver","sportmonks_motorsport_get_driver_standings","sportmonks_motorsport_get_driver_standings_by_season","sportmonks_motorsport_get_drivers","sportmonks_motorsport_get_drivers_by_country","sportmonks_motorsport_get_drivers_by_season","sportmonks_motorsport_get_fixture","sportmonks_motorsport_get_fixtures_by_date","sportmonks_motorsport_get_fixtures_by_date_range","sportmonks_motorsport_get_fixtures_by_ids","sportmonks_motorsport_get_laps_by_fixture","sportmonks_motorsport_get_laps_by_fixture_and_driver","sportmonks_motorsport_get_laps_by_fixture_and_lap","sportmonks_motorsport_get_latest_laps_by_fixture","sportmonks_motorsport_get_latest_pitstops_by_fixture","sportmonks_motorsport_get_latest_stints_by_fixture","sportmonks_motorsport_get_latest_updated_drivers","sportmonks_motorsport_get_latest_updated_fixtures","sportmonks_motorsport_get_league","sportmonks_motorsport_get_leagues","sportmonks_motorsport_get_leagues_by_country","sportmonks_motorsport_get_leagues_by_date","sportmonks_motorsport_get_leagues_by_live","sportmonks_motorsport_get_leagues_by_team","sportmonks_motorsport_get_livescores","sportmonks_motorsport_get_pitstops_by_fixture","sportmonks_motorsport_get_pitstops_by_fixture_and_driver","sportmonks_motorsport_get_pitstops_by_fixture_and_lap","sportmonks_motorsport_get_race_results_by_season_and_driver","sportmonks_motorsport_get_race_results_by_season_and_team","sportmonks_motorsport_get_schedules_by_season","sportmonks_motorsport_get_season","sportmonks_motorsport_get_seasons","sportmonks_motorsport_get_stage","sportmonks_motorsport_get_stages","sportmonks_motorsport_get_stages_by_season","sportmonks_motorsport_get_state","sportmonks_motorsport_get_states","sportmonks_motorsport_get_stints_by_fixture","sportmonks_motorsport_get_stints_by_fixture_and_driver","sportmonks_motorsport_get_stints_by_fixture_and_stint","sportmonks_motorsport_get_team","sportmonks_motorsport_get_team_standings","sportmonks_motorsport_get_team_standings_by_season","sportmonks_motorsport_get_teams","sportmonks_motorsport_get_teams_by_country","sportmonks_motorsport_get_teams_by_season","sportmonks_motorsport_get_venue","sportmonks_motorsport_get_venues","sportmonks_motorsport_get_venues_by_season","sportmonks_motorsport_search_drivers","sportmonks_motorsport_search_leagues","sportmonks_motorsport_search_stages","sportmonks_motorsport_search_teams","sportmonks_motorsport_search_venues","sportmonks_odds_get_all_historical_odds","sportmonks_odds_get_all_inplay_odds","sportmonks_odds_get_all_pre_match_odds","sportmonks_odds_get_all_premium_odds","sportmonks_odds_get_bookmaker","sportmonks_odds_get_bookmaker_event_ids_by_fixture","sportmonks_odds_get_bookmakers","sportmonks_odds_get_bookmakers_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture_and_bookmaker","sportmonks_odds_get_inplay_odds_by_fixture_and_market","sportmonks_odds_get_last_updated_inplay_odds","sportmonks_odds_get_last_updated_pre_match_odds","sportmonks_odds_get_market","sportmonks_odds_get_markets","sportmonks_odds_get_pre_match_odds_by_fixture","sportmonks_odds_get_pre_match_odds_by_fixture_and_bookmaker","sportmonks_odds_get_pre_match_odds_by_fixture_and_market","sportmonks_odds_get_premium_odds_by_fixture","sportmonks_odds_get_premium_odds_by_fixture_and_bookmaker","sportmonks_odds_get_premium_odds_by_fixture_and_market","sportmonks_odds_get_updated_historical_odds_between","sportmonks_odds_get_updated_premium_odds_between","sportmonks_odds_search_bookmakers","sportmonks_odds_search_markets","spotify_add_playlist_cover","spotify_add_to_queue","spotify_add_tracks_to_playlist","spotify_check_following","spotify_check_playlist_followers","spotify_check_saved_albums","spotify_check_saved_audiobooks","spotify_check_saved_episodes","spotify_check_saved_shows","spotify_check_saved_tracks","spotify_create_playlist","spotify_follow_artists","spotify_follow_playlist","spotify_get_album","spotify_get_album_tracks","spotify_get_albums","spotify_get_artist","spotify_get_artist_albums","spotify_get_artist_top_tracks","spotify_get_artists","spotify_get_audiobook","spotify_get_audiobook_chapters","spotify_get_audiobooks","spotify_get_categories","spotify_get_current_user","spotify_get_currently_playing","spotify_get_devices","spotify_get_episode","spotify_get_episodes","spotify_get_followed_artists","spotify_get_markets","spotify_get_new_releases","spotify_get_playback_state","spotify_get_playlist","spotify_get_playlist_cover","spotify_get_playlist_tracks","spotify_get_queue","spotify_get_recently_played","spotify_get_saved_albums","spotify_get_saved_audiobooks","spotify_get_saved_episodes","spotify_get_saved_shows","spotify_get_saved_tracks","spotify_get_show","spotify_get_show_episodes","spotify_get_shows","spotify_get_top_artists","spotify_get_top_tracks","spotify_get_track","spotify_get_tracks","spotify_get_user_playlists","spotify_get_user_profile","spotify_pause","spotify_play","spotify_remove_saved_albums","spotify_remove_saved_audiobooks","spotify_remove_saved_episodes","spotify_remove_saved_shows","spotify_remove_saved_tracks","spotify_remove_tracks_from_playlist","spotify_reorder_playlist_items","spotify_replace_playlist_items","spotify_save_albums","spotify_save_audiobooks","spotify_save_episodes","spotify_save_shows","spotify_save_tracks","spotify_search","spotify_seek","spotify_set_repeat","spotify_set_shuffle","spotify_set_volume","spotify_skip_next","spotify_skip_previous","spotify_transfer_playback","spotify_unfollow_artists","spotify_unfollow_playlist","spotify_update_playlist","sqs_cancel_message_move_task","sqs_change_message_visibility","sqs_change_message_visibility_batch","sqs_create_queue","sqs_delete_message","sqs_delete_message_batch","sqs_delete_queue","sqs_get_queue_attributes","sqs_get_queue_url","sqs_list_dead_letter_source_queues","sqs_list_message_move_tasks","sqs_list_queue_tags","sqs_list_queues","sqs_purge_queue","sqs_receive_message","sqs_send","sqs_send_message_batch","sqs_set_queue_attributes","sqs_start_message_move_task","sqs_tag_queue","sqs_untag_queue","square_batch_retrieve_inventory_counts","square_cancel_invoice","square_cancel_payment","square_complete_payment","square_create_catalog_image","square_create_customer","square_create_invoice","square_create_order","square_create_payment","square_delete_catalog_object","square_delete_customer","square_delete_invoice","square_get_catalog_object","square_get_customer","square_get_invoice","square_get_location","square_get_order","square_get_payment","square_get_refund","square_list_catalog","square_list_customers","square_list_invoices","square_list_locations","square_list_payments","square_list_refunds","square_pay_order","square_publish_invoice","square_refund_payment","square_search_catalog_objects","square_search_customers","square_search_invoices","square_search_orders","square_update_customer","square_upsert_catalog_object","ssh_check_command_exists","ssh_check_file_exists","ssh_create_directory","ssh_delete_file","ssh_download_file","ssh_download_file_v2","ssh_execute_command","ssh_execute_script","ssh_get_system_info","ssh_list_directory","ssh_move_rename","ssh_read_file_content","ssh_upload_file","ssh_write_file_content","ssm_cancel_command","ssm_delete_parameter","ssm_describe_automation_executions","ssm_describe_instance_information","ssm_describe_instance_patch_states","ssm_describe_instance_patches","ssm_describe_parameters","ssm_get_automation_execution","ssm_get_command_invocation","ssm_get_document","ssm_get_parameter","ssm_get_parameters","ssm_get_parameters_by_path","ssm_list_command_invocations","ssm_list_commands","ssm_list_compliance_items","ssm_list_compliance_summaries","ssm_list_documents","ssm_put_parameter","ssm_send_command","ssm_start_automation_execution","ssm_stop_automation_execution","stagehand_agent","stagehand_extract","stripe_cancel_payment_intent","stripe_cancel_subscription","stripe_capture_charge","stripe_capture_payment_intent","stripe_confirm_payment_intent","stripe_create_charge","stripe_create_customer","stripe_create_invoice","stripe_create_payment_intent","stripe_create_price","stripe_create_product","stripe_create_subscription","stripe_delete_customer","stripe_delete_invoice","stripe_delete_product","stripe_finalize_invoice","stripe_list_charges","stripe_list_customers","stripe_list_events","stripe_list_invoices","stripe_list_payment_intents","stripe_list_prices","stripe_list_products","stripe_list_subscriptions","stripe_pay_invoice","stripe_resume_subscription","stripe_retrieve_charge","stripe_retrieve_customer","stripe_retrieve_event","stripe_retrieve_invoice","stripe_retrieve_payment_intent","stripe_retrieve_price","stripe_retrieve_product","stripe_retrieve_subscription","stripe_search_charges","stripe_search_customers","stripe_search_invoices","stripe_search_payment_intents","stripe_search_prices","stripe_search_products","stripe_search_subscriptions","stripe_send_invoice","stripe_update_charge","stripe_update_customer","stripe_update_invoice","stripe_update_payment_intent","stripe_update_price","stripe_update_product","stripe_update_subscription","stripe_void_invoice","sts_assume_role","sts_assume_role_with_saml","sts_assume_role_with_web_identity","sts_get_access_key_info","sts_get_caller_identity","sts_get_session_token","stt_assemblyai","stt_assemblyai_v2","stt_deepgram","stt_deepgram_v2","stt_elevenlabs","stt_elevenlabs_v2","stt_gemini","stt_gemini_v2","stt_whisper","stt_whisper_v2","supabase_count","supabase_delete","supabase_get_row","supabase_insert","supabase_introspect","supabase_invoke_function","supabase_query","supabase_rpc","supabase_storage_copy","supabase_storage_create_bucket","supabase_storage_create_signed_upload_url","supabase_storage_create_signed_url","supabase_storage_delete","supabase_storage_delete_bucket","supabase_storage_download","supabase_storage_empty_bucket","supabase_storage_get_public_url","supabase_storage_list","supabase_storage_list_buckets","supabase_storage_move","supabase_storage_update_bucket","supabase_storage_upload","supabase_text_search","supabase_update","supabase_upsert","supabase_vector_search","table_batch_insert_rows","table_create","table_delete_row","table_delete_rows_by_filter","table_get_row","table_get_schema","table_insert_row","table_list","table_query_rows","table_query_rows_v2","table_update_row","table_update_rows_by_filter","table_upsert_row","tailscale_authorize_device","tailscale_create_auth_key","tailscale_delete_auth_key","tailscale_delete_device","tailscale_delete_user","tailscale_expire_device_key","tailscale_get_acl","tailscale_get_auth_key","tailscale_get_device","tailscale_get_device_routes","tailscale_get_dns_preferences","tailscale_get_dns_searchpaths","tailscale_list_auth_keys","tailscale_list_devices","tailscale_list_dns_nameservers","tailscale_list_users","tailscale_set_acl","tailscale_set_device_routes","tailscale_set_device_tags","tailscale_set_dns_nameservers","tailscale_set_dns_preferences","tailscale_set_dns_searchpaths","tailscale_suspend_user","tailscale_update_device_key","tavily_crawl","tavily_extract","tavily_map","tavily_search","telegram_copy_message","telegram_delete_message","telegram_edit_message_text","telegram_forward_message","telegram_get_chat","telegram_get_chat_member","telegram_message","telegram_pin_message","telegram_send_animation","telegram_send_audio","telegram_send_chat_action","telegram_send_contact","telegram_send_document","telegram_send_location","telegram_send_photo","telegram_send_poll","telegram_send_video","telegram_set_message_reaction","telegram_unpin_message","temporal_cancel_workflow","temporal_count_workflows","temporal_create_schedule","temporal_delete_schedule","temporal_describe_schedule","temporal_describe_task_queue","temporal_describe_workflow","temporal_get_workflow_history","temporal_list_schedules","temporal_list_workflows","temporal_pause_schedule","temporal_query_workflow","temporal_reset_workflow","temporal_signal_with_start","temporal_signal_workflow","temporal_start_workflow","temporal_terminate_workflow","temporal_trigger_schedule","temporal_unpause_schedule","temporal_update_workflow","textract_analyze_expense","textract_analyze_id","textract_parser","textract_parser_v2","thinking_tool","thrive_add_audience_managers","thrive_add_audience_members","thrive_add_user_tags","thrive_create_assignment","thrive_create_audience","thrive_create_completion","thrive_create_user","thrive_delete_assignment","thrive_delete_audience","thrive_delete_user","thrive_get_activity","thrive_get_assignment","thrive_get_audience","thrive_get_completion","thrive_get_content","thrive_get_cpd_category","thrive_get_cpd_entry","thrive_get_cpd_requirement","thrive_get_enrolment","thrive_get_skill_levels","thrive_get_tag","thrive_get_user_by_id","thrive_get_user_by_ref","thrive_list_assignments","thrive_list_audience_managers","thrive_list_audience_members","thrive_list_audiences","thrive_list_completions","thrive_list_enrolments","thrive_list_tags","thrive_query_activities","thrive_query_content","thrive_query_cpd_categories","thrive_query_cpd_entries","thrive_query_cpd_requirements","thrive_query_cpd_user_summaries","thrive_remove_audience_manager","thrive_remove_audience_member","thrive_remove_user_tags","thrive_replace_audience_managers","thrive_replace_audience_members","thrive_search_users","thrive_suspend_user","thrive_update_assignment","thrive_update_audience","thrive_update_user","thrive_update_user_skills","tiktok_get_post_status","tiktok_get_user","tiktok_list_videos","tiktok_query_videos","tiktok_upload_video_draft","tinybird_append_datasource","tinybird_delete_datasource_rows","tinybird_events","tinybird_get_job","tinybird_query","tinybird_query_pipe","tinybird_truncate_datasource","tinyfish_cancel_run","tinyfish_fetch","tinyfish_get_run","tinyfish_list_profiles","tinyfish_list_runs","tinyfish_list_vault_items","tinyfish_run","tinyfish_run_async","tinyfish_search","trello_add_checklist","trello_add_checklist_item","trello_add_comment","trello_add_label","trello_add_member","trello_create_board","trello_create_card","trello_create_list","trello_delete_card","trello_get_actions","trello_get_board","trello_get_card","trello_list_cards","trello_list_lists","trello_list_members","trello_remove_label","trello_remove_member","trello_search","trello_update_card","trello_update_checklist_item","trello_update_list","trigger_dev_activate_schedule","trigger_dev_add_run_tags","trigger_dev_batch_trigger_task","trigger_dev_cancel_run","trigger_dev_complete_waitpoint_token","trigger_dev_create_env_var","trigger_dev_create_schedule","trigger_dev_create_waitpoint_token","trigger_dev_deactivate_schedule","trigger_dev_delete_env_var","trigger_dev_delete_schedule","trigger_dev_execute_query","trigger_dev_get_batch","trigger_dev_get_batch_results","trigger_dev_get_deployment","trigger_dev_get_env_var","trigger_dev_get_latest_deployment","trigger_dev_get_query_schema","trigger_dev_get_queue","trigger_dev_get_run","trigger_dev_get_run_events","trigger_dev_get_run_result","trigger_dev_get_run_trace","trigger_dev_get_schedule","trigger_dev_get_waitpoint_token","trigger_dev_import_env_vars","trigger_dev_list_deployments","trigger_dev_list_env_vars","trigger_dev_list_queues","trigger_dev_list_runs","trigger_dev_list_schedules","trigger_dev_list_timezones","trigger_dev_list_waitpoint_tokens","trigger_dev_override_queue_concurrency","trigger_dev_pause_queue","trigger_dev_promote_deployment","trigger_dev_replay_run","trigger_dev_reschedule_run","trigger_dev_reset_queue_concurrency","trigger_dev_resume_queue","trigger_dev_trigger_task","trigger_dev_update_env_var","trigger_dev_update_run_metadata","trigger_dev_update_schedule","tts_azure","tts_cartesia","tts_deepgram","tts_elevenlabs","tts_google","tts_openai","tts_playht","twilio_send_sms","twilio_voice_get_recording","twilio_voice_list_calls","twilio_voice_make_call","typeform_create_form","typeform_delete_form","typeform_files","typeform_get_form","typeform_insights","typeform_list_forms","typeform_responses","typeform_update_form","upstash_redis_command","upstash_redis_delete","upstash_redis_exists","upstash_redis_expire","upstash_redis_get","upstash_redis_hget","upstash_redis_hgetall","upstash_redis_hset","upstash_redis_incr","upstash_redis_incrby","upstash_redis_keys","upstash_redis_lpush","upstash_redis_lrange","upstash_redis_set","upstash_redis_setnx","upstash_redis_ttl","uptimerobot_create_alert_contact","uptimerobot_create_maintenance_window","uptimerobot_create_monitor","uptimerobot_create_psp","uptimerobot_delete_alert_contact","uptimerobot_delete_maintenance_window","uptimerobot_delete_monitor","uptimerobot_delete_psp","uptimerobot_get_account","uptimerobot_get_alert_contact","uptimerobot_get_incident","uptimerobot_get_maintenance_window","uptimerobot_get_monitor","uptimerobot_get_psp","uptimerobot_list_alert_contacts","uptimerobot_list_incidents","uptimerobot_list_maintenance_windows","uptimerobot_list_monitors","uptimerobot_list_psps","uptimerobot_pause_monitor","uptimerobot_start_monitor","uptimerobot_update_maintenance_window","uptimerobot_update_monitor","uptimerobot_update_psp","vanta_download_document_file","vanta_get_control","vanta_get_document","vanta_get_framework","vanta_get_person","vanta_get_policy","vanta_get_risk_scenario","vanta_get_test","vanta_get_vendor","vanta_get_vulnerable_asset","vanta_list_control_documents","vanta_list_control_tests","vanta_list_controls","vanta_list_document_uploads","vanta_list_documents","vanta_list_framework_controls","vanta_list_frameworks","vanta_list_monitored_computers","vanta_list_people","vanta_list_policies","vanta_list_risk_scenarios","vanta_list_test_entities","vanta_list_tests","vanta_list_vendors","vanta_list_vulnerabilities","vanta_list_vulnerability_remediations","vanta_list_vulnerable_assets","vanta_submit_document","vanta_upload_document_file","vercel_add_domain","vercel_add_project_domain","vercel_cancel_deployment","vercel_create_alias","vercel_create_check","vercel_create_deployment","vercel_create_dns_record","vercel_create_edge_config","vercel_create_env_var","vercel_create_project","vercel_create_webhook","vercel_delete_alias","vercel_delete_deployment","vercel_delete_dns_record","vercel_delete_domain","vercel_delete_edge_config","vercel_delete_env_var","vercel_delete_project","vercel_delete_webhook","vercel_get_alias","vercel_get_check","vercel_get_deployment","vercel_get_deployment_events","vercel_get_domain","vercel_get_domain_config","vercel_get_edge_config","vercel_get_edge_config_items","vercel_get_env_vars","vercel_get_project","vercel_get_team","vercel_get_user","vercel_get_webhook","vercel_list_aliases","vercel_list_checks","vercel_list_deployment_files","vercel_list_deployments","vercel_list_dns_records","vercel_list_domains","vercel_list_edge_configs","vercel_list_project_domains","vercel_list_projects","vercel_list_team_members","vercel_list_teams","vercel_list_webhooks","vercel_pause_project","vercel_promote_deployment","vercel_remove_project_domain","vercel_rerequest_check","vercel_unpause_project","vercel_update_check","vercel_update_dns_record","vercel_update_edge_config_items","vercel_update_env_var","vercel_update_project","vercel_update_project_domain","vercel_verify_project_domain","video_falai","video_luma","video_minimax","video_runway","video_veo","vision_tool","vision_tool_v2","wealthbox_read_contact","wealthbox_read_note","wealthbox_read_task","wealthbox_write_contact","wealthbox_write_note","wealthbox_write_task","webflow_create_item","webflow_delete_item","webflow_get_item","webflow_list_items","webflow_update_item","webhook_request","whatsapp_get_media","whatsapp_mark_read","whatsapp_send_interactive","whatsapp_send_media","whatsapp_send_message","whatsapp_send_reaction","whatsapp_send_template","whatsapp_upload_media","wikipedia_content","wikipedia_random","wikipedia_search","wikipedia_summary","windchill_check_in_document","windchill_check_in_documents","windchill_check_out_document","windchill_check_out_documents","windchill_create_document","windchill_create_documents","windchill_delete_document","windchill_delete_documents","windchill_download_attachment","windchill_download_primary_content","windchill_get_document","windchill_get_document_structure","windchill_get_primary_content","windchill_get_valid_state_transitions","windchill_list_attachments","windchill_list_documents","windchill_revise_document","windchill_revise_documents","windchill_set_lifecycle_state","windchill_undo_check_out_document","windchill_undo_check_out_documents","windchill_update_common_properties","windchill_update_document","windchill_update_document_security_labels","windchill_update_documents","windchill_upload_attachments","windchill_upload_primary_content","wiza_company_enrichment","wiza_get_credits","wiza_individual_reveal","wiza_prospect_search","wordpress_create_category","wordpress_create_comment","wordpress_create_page","wordpress_create_post","wordpress_create_tag","wordpress_delete_category","wordpress_delete_comment","wordpress_delete_media","wordpress_delete_page","wordpress_delete_post","wordpress_delete_tag","wordpress_get_category","wordpress_get_current_user","wordpress_get_media","wordpress_get_page","wordpress_get_post","wordpress_get_tag","wordpress_get_user","wordpress_list_categories","wordpress_list_comments","wordpress_list_media","wordpress_list_pages","wordpress_list_posts","wordpress_list_tags","wordpress_list_users","wordpress_search_content","wordpress_update_category","wordpress_update_comment","wordpress_update_page","wordpress_update_post","wordpress_update_tag","wordpress_upload_media","workday_assign_onboarding","workday_change_job","workday_create_prehire","workday_get_compensation","workday_get_organizations","workday_get_worker","workday_hire_employee","workday_list_workers","workday_terminate_worker","workday_update_worker","workflow_executor","x_create_bookmark","x_create_tweet","x_delete_bookmark","x_delete_tweet","x_get_blocking","x_get_bookmarks","x_get_followers","x_get_following","x_get_liked_tweets","x_get_liking_users","x_get_me","x_get_personalized_trends","x_get_quote_tweets","x_get_retweeted_by","x_get_trends_by_woeid","x_get_tweets_by_ids","x_get_usage","x_get_user_mentions","x_get_user_timeline","x_get_user_tweets","x_hide_reply","x_manage_block","x_manage_follow","x_manage_like","x_manage_mute","x_manage_retweet","x_read","x_search","x_search_tweets","x_search_users","x_user","x_write","youtube_channel_info","youtube_channel_playlists","youtube_channel_videos","youtube_comments","youtube_playlist_items","youtube_search","youtube_trending","youtube_video_categories","youtube_video_details","zendesk_autocomplete_organizations","zendesk_create_organization","zendesk_create_organizations_bulk","zendesk_create_ticket","zendesk_create_tickets_bulk","zendesk_create_user","zendesk_create_users_bulk","zendesk_delete_organization","zendesk_delete_ticket","zendesk_delete_user","zendesk_get_current_user","zendesk_get_organization","zendesk_get_organizations","zendesk_get_ticket","zendesk_get_tickets","zendesk_get_user","zendesk_get_users","zendesk_merge_tickets","zendesk_search","zendesk_search_count","zendesk_search_users","zendesk_update_organization","zendesk_update_ticket","zendesk_update_tickets_bulk","zendesk_update_user","zendesk_update_users_bulk","zep_add_messages","zep_add_user","zep_create_thread","zep_delete_thread","zep_get_context","zep_get_messages","zep_get_threads","zep_get_user","zep_get_user_threads","zerobounce_get_credits","zerobounce_verify_email","zoho_desk_add_comment","zoho_desk_get_attachment","zoho_desk_get_contact","zoho_desk_get_thread","zoho_desk_get_ticket","zoho_desk_list_comments","zoho_desk_list_organizations","zoho_desk_list_threads","zoho_desk_list_tickets","zoho_desk_update_ticket","zoom_create_meeting","zoom_delete_meeting","zoom_delete_recording","zoom_get_meeting","zoom_get_meeting_invitation","zoom_get_meeting_recordings","zoom_list_meetings","zoom_list_past_participants","zoom_list_recordings","zoom_update_meeting","zoominfo_enrich_companies","zoominfo_enrich_contacts","zoominfo_search_companies","zoominfo_search_contacts","zoominfo_search_intent","zoominfo_search_news"]' + '["a2a_cancel_task","a2a_get_agent_card","a2a_get_task","a2a_send_message","affinity_batch_update_entity_fields","affinity_batch_update_list_entry_fields","affinity_create_list","affinity_create_list_field_dropdown_option","affinity_create_merge","affinity_create_note","affinity_create_reminder","affinity_delete_list_field_dropdown_option","affinity_delete_note","affinity_get_company","affinity_get_current_user","affinity_get_entity_field_value","affinity_get_list","affinity_get_list_entry","affinity_get_list_entry_field","affinity_get_list_field_dropdown_option","affinity_get_merge","affinity_get_merge_task","affinity_get_note","affinity_get_opportunity","affinity_get_person","affinity_get_saved_view","affinity_get_transcript","affinity_get_user","affinity_list_calls","affinity_list_chat_messages","affinity_list_companies","affinity_list_coworker_connections","affinity_list_emails","affinity_list_entity_field_values","affinity_list_entity_list_entries","affinity_list_entity_lists","affinity_list_entity_notes","affinity_list_entity_relationships","affinity_list_field_dropdown_options","affinity_list_field_metadata","affinity_list_field_value_changes","affinity_list_investor_executive_connections","affinity_list_list_entries","affinity_list_list_entry_field_value_changes","affinity_list_list_entry_fields","affinity_list_list_field_dropdown_options","affinity_list_list_fields","affinity_list_lists","affinity_list_meetings","affinity_list_merge_tasks","affinity_list_merges","affinity_list_note_attached_companies","affinity_list_note_attached_opportunities","affinity_list_note_attached_persons","affinity_list_note_replies","affinity_list_notes","affinity_list_opportunities","affinity_list_persons","affinity_list_reminders","affinity_list_saved_view_entries","affinity_list_saved_views","affinity_list_transcript_fragments","affinity_list_transcripts","affinity_list_users","affinity_search_companies","affinity_search_files","affinity_search_list_entries","affinity_search_notes","affinity_search_persons","affinity_semantic_search","affinity_update_entity_field_value","affinity_update_list_entry_field","affinity_update_list_field_dropdown_option","affinity_update_note","agentmail_create_draft","agentmail_create_inbox","agentmail_delete_draft","agentmail_delete_inbox","agentmail_delete_thread","agentmail_forward_message","agentmail_get_draft","agentmail_get_inbox","agentmail_get_message","agentmail_get_thread","agentmail_list_drafts","agentmail_list_inboxes","agentmail_list_messages","agentmail_list_threads","agentmail_reply_message","agentmail_send_draft","agentmail_send_message","agentmail_update_draft","agentmail_update_inbox","agentmail_update_message","agentmail_update_thread","agentphone_create_call","agentphone_create_contact","agentphone_create_number","agentphone_delete_contact","agentphone_get_call","agentphone_get_call_transcript","agentphone_get_contact","agentphone_get_conversation","agentphone_get_conversation_messages","agentphone_get_number_messages","agentphone_get_usage","agentphone_get_usage_daily","agentphone_get_usage_monthly","agentphone_list_calls","agentphone_list_contacts","agentphone_list_conversations","agentphone_list_numbers","agentphone_react_to_message","agentphone_release_number","agentphone_send_message","agentphone_update_contact","agentphone_update_conversation","agiloft_async_status","agiloft_attach_file","agiloft_attachment_info","agiloft_create_record","agiloft_delete_record","agiloft_get_choice_line_id","agiloft_list_tables","agiloft_lock_record","agiloft_nlp_search","agiloft_read_record","agiloft_remove_attachment","agiloft_retrieve_attachment","agiloft_run_action_button","agiloft_saved_search","agiloft_search_records","agiloft_select_records","agiloft_update_record","agiloft_upsert_record","ahrefs_anchors","ahrefs_backlinks","ahrefs_backlinks_stats","ahrefs_batch_analysis","ahrefs_broken_backlinks","ahrefs_domain_rating","ahrefs_domain_rating_history","ahrefs_keyword_overview","ahrefs_keywords_history","ahrefs_metrics","ahrefs_metrics_history","ahrefs_organic_competitors","ahrefs_organic_keywords","ahrefs_paid_pages","ahrefs_rank_tracker_competitors_overview","ahrefs_rank_tracker_competitors_stats","ahrefs_rank_tracker_overview","ahrefs_rank_tracker_serp_overview","ahrefs_refdomains_history","ahrefs_referring_domains","ahrefs_related_terms","ahrefs_site_audit_page_explorer","ahrefs_top_pages","airtable_create_records","airtable_delete_records","airtable_get_base_schema","airtable_get_record","airtable_list_bases","airtable_list_records","airtable_list_tables","airtable_update_multiple_records","airtable_update_record","airtable_upsert_records","airweave_search","algolia_add_record","algolia_batch_operations","algolia_browse_records","algolia_clear_records","algolia_copy_move_index","algolia_delete_by_filter","algolia_delete_index","algolia_delete_record","algolia_get_record","algolia_get_records","algolia_get_settings","algolia_get_task_status","algolia_list_indices","algolia_partial_update_record","algolia_search","algolia_update_settings","amplitude_event_segmentation","amplitude_funnels","amplitude_get_active_users","amplitude_get_revenue","amplitude_group_identify","amplitude_identify_user","amplitude_list_events","amplitude_realtime_active_users","amplitude_retention","amplitude_send_event","amplitude_user_activity","amplitude_user_profile","amplitude_user_search","apify_get_dataset_items","apify_get_run","apify_run_actor_async","apify_run_actor_sync","apify_run_task","apollo_account_bulk_create","apollo_account_bulk_update","apollo_account_create","apollo_account_search","apollo_account_update","apollo_contact_bulk_create","apollo_contact_bulk_update","apollo_contact_create","apollo_contact_search","apollo_contact_update","apollo_email_accounts","apollo_opportunity_create","apollo_opportunity_get","apollo_opportunity_search","apollo_opportunity_update","apollo_organization_bulk_enrich","apollo_organization_enrich","apollo_organization_search","apollo_people_bulk_enrich","apollo_people_enrich","apollo_people_search","apollo_sequence_add_contacts","apollo_sequence_search","apollo_task_create","apollo_task_search","appconfig_create_application","appconfig_create_configuration_profile","appconfig_create_environment","appconfig_create_hosted_configuration_version","appconfig_delete_application","appconfig_delete_configuration_profile","appconfig_delete_environment","appconfig_delete_hosted_configuration_version","appconfig_get_application","appconfig_get_configuration","appconfig_get_configuration_profile","appconfig_get_deployment","appconfig_get_environment","appconfig_get_hosted_configuration_version","appconfig_list_applications","appconfig_list_configuration_profiles","appconfig_list_deployment_strategies","appconfig_list_deployments","appconfig_list_environments","appconfig_list_hosted_configuration_versions","appconfig_start_deployment","appconfig_stop_deployment","appconfig_update_application","appconfig_update_configuration_profile","appconfig_update_environment","arxiv_get_author_papers","arxiv_get_paper","arxiv_search","asana_add_comment","asana_add_followers","asana_create_project","asana_create_section","asana_create_subtask","asana_create_task","asana_delete_task","asana_get_project","asana_get_projects","asana_get_task","asana_list_sections","asana_list_workspaces","asana_search_tasks","asana_update_task","ashby_add_candidate_tag","ashby_anonymize_candidate","ashby_change_application_source","ashby_change_application_stage","ashby_create_application","ashby_create_candidate","ashby_create_note","ashby_delete_application","ashby_get_application","ashby_get_candidate","ashby_get_job","ashby_get_job_posting","ashby_get_offer","ashby_get_opening","ashby_list_application_feedback","ashby_list_application_history","ashby_list_applications","ashby_list_archive_reasons","ashby_list_candidate_tags","ashby_list_candidates","ashby_list_custom_fields","ashby_list_departments","ashby_list_interview_plans","ashby_list_interview_stages","ashby_list_interviews","ashby_list_job_postings","ashby_list_jobs","ashby_list_locations","ashby_list_notes","ashby_list_offers","ashby_list_openings","ashby_list_sources","ashby_list_users","ashby_remove_candidate_tag","ashby_search_candidates","ashby_search_jobs","ashby_search_openings","ashby_search_users","ashby_set_custom_field_value","ashby_set_custom_field_values","ashby_transfer_application","ashby_update_candidate","ashby_upload_candidate_file","ashby_upload_resume","athena_batch_get_named_query","athena_batch_get_prepared_statement","athena_batch_get_query_execution","athena_create_named_query","athena_create_prepared_statement","athena_delete_named_query","athena_delete_prepared_statement","athena_get_data_catalog","athena_get_database","athena_get_named_query","athena_get_prepared_statement","athena_get_query_execution","athena_get_query_results","athena_get_query_runtime_statistics","athena_get_table_metadata","athena_get_work_group","athena_list_data_catalogs","athena_list_databases","athena_list_named_queries","athena_list_prepared_statements","athena_list_query_executions","athena_list_table_metadata","athena_list_work_groups","athena_start_query","athena_stop_query","athena_update_named_query","athena_update_prepared_statement","attio_assert_record","attio_create_attribute","attio_create_comment","attio_create_list","attio_create_list_entry","attio_create_note","attio_create_object","attio_create_record","attio_create_task","attio_create_webhook","attio_delete_comment","attio_delete_list_entry","attio_delete_note","attio_delete_record","attio_delete_task","attio_delete_webhook","attio_get_attribute","attio_get_comment","attio_get_list","attio_get_list_entry","attio_get_member","attio_get_note","attio_get_object","attio_get_record","attio_get_task","attio_get_thread","attio_get_webhook","attio_list_attributes","attio_list_lists","attio_list_members","attio_list_notes","attio_list_objects","attio_list_records","attio_list_tasks","attio_list_threads","attio_list_webhooks","attio_query_list_entries","attio_search_records","attio_update_attribute","attio_update_list","attio_update_list_entry","attio_update_object","attio_update_record","attio_update_task","attio_update_webhook","azure_data_explorer_create_table","azure_data_explorer_drop_table","azure_data_explorer_ingest_from_query","azure_data_explorer_ingest_inline","azure_data_explorer_list_databases","azure_data_explorer_list_functions","azure_data_explorer_list_tables","azure_data_explorer_management","azure_data_explorer_query","azure_data_explorer_show_database_schema","azure_data_explorer_show_ingestion_failures","azure_data_explorer_show_operations","azure_data_explorer_show_table_details","azure_data_explorer_show_table_schema","azure_devops_add_comment","azure_devops_create_work_item","azure_devops_get_build_log","azure_devops_get_build_timeline","azure_devops_get_comments","azure_devops_get_pipeline","azure_devops_get_pipeline_run","azure_devops_get_work_item","azure_devops_get_work_items_batch","azure_devops_get_work_items_between_builds","azure_devops_list_build_logs","azure_devops_list_builds","azure_devops_list_pipeline_runs","azure_devops_list_pipelines","azure_devops_query_work_items","azure_devops_update_work_item","bitbucket_approve_pull_request","bitbucket_create_branch","bitbucket_create_pull_request","bitbucket_create_pull_request_comment","bitbucket_decline_pull_request","bitbucket_delete_branch","bitbucket_get_commit","bitbucket_get_file","bitbucket_get_file_metadata","bitbucket_get_pipeline","bitbucket_get_pipeline_step_log","bitbucket_get_pull_request","bitbucket_get_pull_request_diff","bitbucket_get_pull_request_diffstat","bitbucket_get_pull_request_merge_task_status","bitbucket_get_repository","bitbucket_list_branches","bitbucket_list_commits","bitbucket_list_directory","bitbucket_list_pipeline_steps","bitbucket_list_pipelines","bitbucket_list_pull_request_comments","bitbucket_list_pull_request_commit_statuses","bitbucket_list_pull_requests","bitbucket_list_repositories","bitbucket_list_workspaces","bitbucket_merge_pull_request","bitbucket_request_pull_request_changes","bitbucket_stop_pipeline","bitbucket_trigger_pipeline","box_copy_file","box_create_folder","box_delete_file","box_delete_folder","box_download_file","box_download_file_v2","box_get_file_info","box_list_folder_items","box_search","box_sign_cancel_request","box_sign_create_request","box_sign_get_request","box_sign_list_requests","box_sign_resend_request","box_update_file","box_upload_file","brandfetch_get_brand","brandfetch_search","brex_archive_budget","brex_create_budget","brex_create_spend_limit","brex_create_transfer","brex_create_vendor","brex_get_budget","brex_get_cash_account","brex_get_company","brex_get_current_user","brex_get_expense","brex_get_spend_limit","brex_get_transfer","brex_get_user","brex_get_vendor","brex_list_budgets","brex_list_card_accounts","brex_list_card_statements","brex_list_card_transactions","brex_list_cards","brex_list_cash_accounts","brex_list_cash_statements","brex_list_cash_transactions","brex_list_departments","brex_list_expenses","brex_list_locations","brex_list_spend_limits","brex_list_titles","brex_list_transfers","brex_list_users","brex_list_vendors","brex_match_receipt","brex_update_expense","brex_update_vendor","brex_upload_receipt","brightdata_cancel_snapshot","brightdata_discover","brightdata_download_snapshot","brightdata_scrape_dataset","brightdata_scrape_url","brightdata_serp_search","brightdata_snapshot_status","brightdata_sync_scrape","browser_use_run_task","buffer_create_idea","buffer_create_post","buffer_delete_post","buffer_edit_post","buffer_get_account","buffer_get_channels","buffer_get_idea_groups","buffer_get_ideas","buffer_get_post","buffer_get_posts","calcom_cancel_booking","calcom_confirm_booking","calcom_create_booking","calcom_create_event_type","calcom_create_schedule","calcom_decline_booking","calcom_delete_event_type","calcom_delete_schedule","calcom_get_booking","calcom_get_default_schedule","calcom_get_event_type","calcom_get_schedule","calcom_get_slots","calcom_list_bookings","calcom_list_event_types","calcom_list_schedules","calcom_reschedule_booking","calcom_update_event_type","calcom_update_schedule","calendly_cancel_event","calendly_create_event_invitee","calendly_create_invitee_no_show","calendly_create_scheduling_link","calendly_create_webhook","calendly_delete_invitee_no_show","calendly_delete_webhook","calendly_get_current_user","calendly_get_event_invitee","calendly_get_event_type","calendly_get_scheduled_event","calendly_get_user","calendly_list_event_invitees","calendly_list_event_type_available_times","calendly_list_event_types","calendly_list_organization_memberships","calendly_list_routing_form_submissions","calendly_list_routing_forms","calendly_list_scheduled_events","calendly_list_user_availability_schedules","calendly_list_user_busy_times","calendly_list_webhooks","cbinsights_chat","cbinsights_get_commercial_maturity_history","cbinsights_get_exit_probability_history","cbinsights_get_mosaic_history","cbinsights_get_org_business_relationships","cbinsights_get_org_funding_window","cbinsights_get_org_fundings","cbinsights_get_org_investments","cbinsights_get_org_management_and_board","cbinsights_get_org_outlook","cbinsights_get_org_portfolio_exits","cbinsights_get_org_revenue","cbinsights_get_scouting_report","cbinsights_get_strategy_map","cbinsights_list_business_relationships","cbinsights_list_funding_window","cbinsights_list_fundings","cbinsights_list_investments","cbinsights_list_management_and_board","cbinsights_list_outlook","cbinsights_list_portfolio_exits","cbinsights_list_revenue","cbinsights_lookup_organizations","cbinsights_rag","cbinsights_search_firmographics","circleback_add_tag_to_meetings","circleback_create_tag","circleback_delete_action_item","circleback_delete_meeting","circleback_delete_tag","circleback_get_company","circleback_get_meeting","circleback_get_person","circleback_get_transcript","circleback_list_action_items","circleback_list_calendar_events","circleback_list_companies","circleback_list_meetings","circleback_list_people","circleback_list_tags","circleback_remove_tag_from_meetings","circleback_search_meetings","circleback_update_action_item","circleback_update_meeting","circleback_update_tag","clay_populate","clerk_add_organization_member","clerk_ban_user","clerk_create_actor_token","clerk_create_allowlist_identifier","clerk_create_blocklist_identifier","clerk_create_organization","clerk_create_organization_invitation","clerk_create_user","clerk_delete_allowlist_identifier","clerk_delete_blocklist_identifier","clerk_delete_organization","clerk_delete_user","clerk_get_jwt_template","clerk_get_organization","clerk_get_session","clerk_get_user","clerk_get_user_oauth_token","clerk_list_allowlist_identifiers","clerk_list_blocklist_identifiers","clerk_list_jwt_templates","clerk_list_organization_invitations","clerk_list_organization_memberships","clerk_list_organizations","clerk_list_sessions","clerk_list_users","clerk_lock_user","clerk_remove_organization_member","clerk_revoke_actor_token","clerk_revoke_session","clerk_unban_user","clerk_unlock_user","clerk_update_organization","clerk_update_organization_membership","clerk_update_user","clickhouse_count_rows","clickhouse_create_database","clickhouse_create_table","clickhouse_delete","clickhouse_describe_table","clickhouse_drop_database","clickhouse_drop_partition","clickhouse_drop_table","clickhouse_execute","clickhouse_insert","clickhouse_insert_rows","clickhouse_introspect","clickhouse_kill_query","clickhouse_list_clusters","clickhouse_list_databases","clickhouse_list_mutations","clickhouse_list_partitions","clickhouse_list_running_queries","clickhouse_list_tables","clickhouse_optimize_table","clickhouse_query","clickhouse_rename_table","clickhouse_show_create_table","clickhouse_table_stats","clickhouse_truncate_table","clickhouse_update","clickup_add_tag_to_task","clickup_create_checklist","clickup_create_checklist_item","clickup_create_comment","clickup_create_folder","clickup_create_list","clickup_create_task","clickup_create_time_entry","clickup_delete_checklist","clickup_delete_checklist_item","clickup_delete_comment","clickup_delete_task","clickup_delete_time_entry","clickup_get_comments","clickup_get_custom_fields","clickup_get_folders","clickup_get_list_members","clickup_get_lists","clickup_get_running_timer","clickup_get_space_tags","clickup_get_spaces","clickup_get_task","clickup_get_task_members","clickup_get_tasks","clickup_get_time_entries","clickup_get_workspaces","clickup_remove_custom_field_value","clickup_remove_tag_from_task","clickup_search_tasks","clickup_set_custom_field_value","clickup_start_timer","clickup_stop_timer","clickup_update_checklist","clickup_update_checklist_item","clickup_update_comment","clickup_update_task","clickup_update_time_entry","clickup_upload_attachment","cloudflare_create_access_application","cloudflare_create_access_policy","cloudflare_create_access_service_token","cloudflare_create_dns_record","cloudflare_create_r2_bucket","cloudflare_create_rate_limit_rule","cloudflare_create_ruleset","cloudflare_create_ruleset_rule","cloudflare_create_zone","cloudflare_delete_access_application","cloudflare_delete_access_policy","cloudflare_delete_dns_record","cloudflare_delete_r2_bucket","cloudflare_delete_ruleset_rule","cloudflare_delete_zone","cloudflare_dns_analytics","cloudflare_get_access_application","cloudflare_get_r2_bucket","cloudflare_get_ruleset","cloudflare_get_ruleset_entrypoint","cloudflare_get_tunnel","cloudflare_get_tunnel_configuration","cloudflare_get_worker_script_settings","cloudflare_get_zone","cloudflare_get_zone_settings","cloudflare_list_access_applications","cloudflare_list_access_groups","cloudflare_list_access_identity_providers","cloudflare_list_access_policies","cloudflare_list_access_service_tokens","cloudflare_list_certificates","cloudflare_list_dns_records","cloudflare_list_managed_ruleset_overrides","cloudflare_list_r2_buckets","cloudflare_list_rate_limit_rules","cloudflare_list_rulesets","cloudflare_list_tunnels","cloudflare_list_worker_routes","cloudflare_list_worker_scripts","cloudflare_list_zones","cloudflare_purge_cache","cloudflare_revoke_access_service_token","cloudflare_update_access_application","cloudflare_update_access_policy","cloudflare_update_dns_record","cloudflare_update_rate_limit_rule","cloudflare_update_ruleset_rule","cloudflare_update_zone_setting","cloudformation_cancel_update_stack","cloudformation_create_change_set","cloudformation_create_stack","cloudformation_delete_stack","cloudformation_describe_change_set","cloudformation_describe_stack_drift_detection_status","cloudformation_describe_stack_events","cloudformation_describe_stacks","cloudformation_detect_stack_drift","cloudformation_execute_change_set","cloudformation_get_template","cloudformation_get_template_summary","cloudformation_list_stack_resources","cloudformation_update_stack","cloudformation_validate_template","cloudtrail_cancel_query","cloudtrail_describe_query","cloudtrail_describe_trails","cloudtrail_get_event_data_store","cloudtrail_get_event_selectors","cloudtrail_get_insight_selectors","cloudtrail_get_query_results","cloudtrail_get_trail","cloudtrail_get_trail_status","cloudtrail_list_event_data_stores","cloudtrail_list_tags","cloudtrail_list_trails","cloudtrail_lookup_events","cloudtrail_start_query","cloudwatch_describe_alarm_history","cloudwatch_describe_alarms","cloudwatch_describe_log_groups","cloudwatch_describe_log_streams","cloudwatch_filter_log_events","cloudwatch_get_log_events","cloudwatch_get_metric_statistics","cloudwatch_list_metrics","cloudwatch_mute_alarm","cloudwatch_put_log_group_retention","cloudwatch_put_metric_data","cloudwatch_query_logs","cloudwatch_unmute_alarm","coda_add_custom_domain","coda_add_permission","coda_change_user_role","coda_create_doc","coda_create_folder","coda_create_page","coda_delete_custom_domain","coda_delete_doc","coda_delete_folder","coda_delete_page","coda_delete_page_content","coda_delete_permission","coda_delete_row","coda_delete_rows","coda_export_page","coda_get_acl_settings","coda_get_analytics_last_updated","coda_get_column","coda_get_control","coda_get_custom_domain_provider","coda_get_doc","coda_get_doc_analytics_summary","coda_get_folder","coda_get_formula","coda_get_mutation_status","coda_get_page","coda_get_page_content","coda_get_page_export_status","coda_get_row","coda_get_sharing_metadata","coda_get_table","coda_list_categories","coda_list_columns","coda_list_controls","coda_list_custom_domains","coda_list_doc_analytics","coda_list_docs","coda_list_folder_children","coda_list_folders","coda_list_formulas","coda_list_page_analytics","coda_list_pages","coda_list_permissions","coda_list_rows","coda_list_tables","coda_list_workspace_members","coda_list_workspace_roles","coda_publish_doc","coda_push_button","coda_resolve_browser_link","coda_search_principals","coda_trigger_automation","coda_unpublish_doc","coda_update_acl_settings","coda_update_doc","coda_update_folder","coda_update_page","coda_update_row","coda_upsert_rows","coda_whoami","codepipeline_disable_stage_transition","codepipeline_enable_stage_transition","codepipeline_get_pipeline","codepipeline_get_pipeline_execution","codepipeline_get_pipeline_state","codepipeline_list_action_executions","codepipeline_list_pipeline_executions","codepipeline_list_pipelines","codepipeline_put_approval_result","codepipeline_retry_stage_execution","codepipeline_start_execution","codepipeline_stop_execution","confluence_add_label","confluence_create_blogpost","confluence_create_comment","confluence_create_page","confluence_create_page_property","confluence_create_space","confluence_create_space_property","confluence_delete_attachment","confluence_delete_blogpost","confluence_delete_comment","confluence_delete_label","confluence_delete_page","confluence_delete_page_property","confluence_delete_space","confluence_delete_space_property","confluence_get_blogpost","confluence_get_page_ancestors","confluence_get_page_children","confluence_get_page_descendants","confluence_get_page_version","confluence_get_pages_by_label","confluence_get_space","confluence_get_task","confluence_get_user","confluence_list_attachments","confluence_list_blogposts","confluence_list_blogposts_in_space","confluence_list_comments","confluence_list_labels","confluence_list_page_properties","confluence_list_page_versions","confluence_list_pages_in_space","confluence_list_space_labels","confluence_list_space_permissions","confluence_list_space_properties","confluence_list_spaces","confluence_list_tasks","confluence_retrieve","confluence_search","confluence_search_in_space","confluence_update","confluence_update_blogpost","confluence_update_comment","confluence_update_space","confluence_update_task","confluence_upload_attachment","context_dev_classify_naics","context_dev_classify_sic","context_dev_crawl","context_dev_extract","context_dev_extract_product","context_dev_extract_products","context_dev_get_brand","context_dev_get_brand_by_email","context_dev_get_brand_by_name","context_dev_get_brand_by_ticker","context_dev_identify_transaction","context_dev_map","context_dev_scrape_fonts","context_dev_scrape_html","context_dev_scrape_images","context_dev_scrape_markdown","context_dev_scrape_styleguide","context_dev_screenshot","context_dev_search","convex_action","convex_document_deltas","convex_list_documents","convex_list_tables","convex_mutation","convex_query","convex_run_function","crowdstrike_create_indicators","crowdstrike_delete_indicators","crowdstrike_delete_rtr_session","crowdstrike_execute_rtr_command","crowdstrike_get_alert_details","crowdstrike_get_case_details","crowdstrike_get_host_group_details","crowdstrike_get_indicator_details","crowdstrike_get_rtr_command_status","crowdstrike_get_sensor_aggregates","crowdstrike_get_sensor_details","crowdstrike_get_vulnerability_details","crowdstrike_init_rtr_session","crowdstrike_perform_host_action","crowdstrike_perform_host_group_action","crowdstrike_query_alerts","crowdstrike_query_cases","crowdstrike_query_host_groups","crowdstrike_query_indicators","crowdstrike_query_sensors","crowdstrike_query_vulnerabilities","crowdstrike_update_alerts","crowdstrike_update_indicators","crunchbase_autocomplete","crunchbase_get_acquisition","crunchbase_get_entity","crunchbase_get_entity_card","crunchbase_get_fields_metadata","crunchbase_get_funding_round","crunchbase_get_organization","crunchbase_get_person","crunchbase_list_deleted_entities","crunchbase_search_acquisitions","crunchbase_search_entities","crunchbase_search_funding_rounds","crunchbase_search_organizations","crunchbase_search_people","cursor_add_followup","cursor_add_followup_v2","cursor_delete_agent","cursor_delete_agent_v2","cursor_download_artifact","cursor_download_artifact_v2","cursor_get_agent","cursor_get_agent_v2","cursor_get_api_key_info","cursor_get_api_key_info_v2","cursor_get_conversation","cursor_get_conversation_v2","cursor_launch_agent","cursor_launch_agent_v2","cursor_list_agents","cursor_list_agents_v2","cursor_list_artifacts","cursor_list_artifacts_v2","cursor_list_models","cursor_list_models_v2","cursor_list_repositories","cursor_list_repositories_v2","cursor_stop_agent","cursor_stop_agent_v2","dagster_delete_run","dagster_get_asset","dagster_get_run","dagster_get_run_logs","dagster_launch_run","dagster_list_assets","dagster_list_jobs","dagster_list_runs","dagster_list_schedules","dagster_list_sensors","dagster_materialize_assets","dagster_reexecute_run","dagster_report_asset_materialization","dagster_start_schedule","dagster_start_sensor","dagster_stop_schedule","dagster_stop_sensor","dagster_terminate_run","dagster_wipe_asset","databricks_cancel_run","databricks_execute_sql","databricks_get_cluster","databricks_get_job","databricks_get_run","databricks_get_run_output","databricks_get_statement","databricks_list_clusters","databricks_list_jobs","databricks_list_runs","databricks_list_warehouses","databricks_run_job","datadog_add_incident_todo","datadog_cancel_downtime","datadog_create_dashboard","datadog_create_downtime","datadog_create_event","datadog_create_incident","datadog_create_monitor","datadog_create_slo","datadog_delete_dashboard","datadog_delete_slo","datadog_get_browser_synthetics_results","datadog_get_dashboard","datadog_get_incident","datadog_get_monitor","datadog_get_security_signal","datadog_get_slo","datadog_get_slo_history","datadog_get_synthetics_results","datadog_get_synthetics_test","datadog_list_dashboards","datadog_list_downtimes","datadog_list_incidents","datadog_list_monitors","datadog_list_security_rules","datadog_list_security_signals","datadog_list_services","datadog_list_slos","datadog_list_synthetics_tests","datadog_mute_monitor","datadog_query_logs","datadog_query_timeseries","datadog_search_spans","datadog_send_logs","datadog_submit_metrics","datadog_trigger_synthetics_tests","datadog_unmute_monitor","datadog_update_incident","datadog_update_security_signal_assignee","datadog_update_security_signal_state","datadog_update_slo","datadog_update_synthetics_status","datagma_enrich_company","datagma_enrich_person","datagma_find_email","datagma_find_phone","datagma_get_credits","daytona_create_sandbox","daytona_delete_sandbox","daytona_download_file","daytona_execute_command","daytona_get_sandbox","daytona_git_clone","daytona_list_files","daytona_list_sandboxes","daytona_run_code","daytona_start_sandbox","daytona_stop_sandbox","daytona_upload_file","deployed_block_executor","deployments_deploy","deployments_get_version","deployments_list_versions","deployments_promote","deployments_undeploy","devin_append_session_tags","devin_archive_session","devin_create_session","devin_get_session","devin_get_session_tags","devin_list_session_attachments","devin_list_session_messages","devin_list_sessions","devin_replace_session_tags","devin_send_message","devin_terminate_session","discord_add_reaction","discord_archive_thread","discord_assign_role","discord_ban_member","discord_bulk_delete_messages","discord_create_channel","discord_create_invite","discord_create_role","discord_create_thread","discord_create_webhook","discord_delete_channel","discord_delete_invite","discord_delete_message","discord_delete_role","discord_delete_webhook","discord_edit_message","discord_execute_webhook","discord_get_channel","discord_get_invite","discord_get_member","discord_get_messages","discord_get_pinned_messages","discord_get_server","discord_get_user","discord_get_webhook","discord_join_thread","discord_kick_member","discord_leave_thread","discord_list_channels","discord_list_roles","discord_pin_message","discord_remove_reaction","discord_remove_role","discord_send_message","discord_unban_member","discord_unpin_message","discord_update_channel","discord_update_member","discord_update_role","docusign_create_from_template","docusign_download_document","docusign_get_envelope","docusign_list_envelopes","docusign_list_recipients","docusign_list_templates","docusign_send_envelope","docusign_void_envelope","downdetector_get_company","downdetector_get_company_attribution","downdetector_get_company_baseline","downdetector_get_company_events","downdetector_get_company_incidents","downdetector_get_company_indicators","downdetector_get_company_last_15","downdetector_get_company_status","downdetector_get_provider","downdetector_get_reports","downdetector_get_site_companies","downdetector_list_categories","downdetector_list_incidents","downdetector_list_sites","downdetector_search_companies","dropbox_copy","dropbox_create_folder","dropbox_create_shared_link","dropbox_delete","dropbox_download","dropbox_download_v2","dropbox_get_metadata","dropbox_list_folder","dropbox_list_revisions","dropbox_list_shared_links","dropbox_move","dropbox_restore","dropbox_search","dropbox_upload","dropcontact_enrich_contact","dspy_chain_of_thought","dspy_predict","dspy_react","dub_bulk_create_links","dub_bulk_delete_links","dub_bulk_update_links","dub_create_link","dub_create_tag","dub_delete_link","dub_get_analytics","dub_get_events","dub_get_link","dub_get_links_count","dub_get_qr_code","dub_get_qr_code_v2","dub_list_domains","dub_list_folders","dub_list_links","dub_list_tags","dub_update_link","dub_upsert_link","duckduckgo_search","dynamodb_delete","dynamodb_get","dynamodb_introspect","dynamodb_put","dynamodb_query","dynamodb_scan","dynamodb_update","dynatrace_add_problem_comment","dynatrace_add_tags","dynatrace_close_problem","dynatrace_create_settings_object","dynatrace_create_slo","dynatrace_delete_problem_comment","dynatrace_delete_settings_object","dynatrace_delete_slo","dynatrace_delete_tag","dynatrace_execute_synthetic_monitors","dynatrace_get_attack","dynatrace_get_audit_logs","dynatrace_get_entity","dynatrace_get_event","dynatrace_get_metric","dynatrace_get_problem","dynatrace_get_problem_comment","dynatrace_get_security_problem","dynatrace_get_settings_object","dynatrace_get_slo","dynatrace_get_synthetic_batch","dynatrace_ingest_event","dynatrace_ingest_logs","dynatrace_ingest_metrics","dynatrace_list_attacks","dynatrace_list_entities","dynatrace_list_entity_types","dynatrace_list_events","dynatrace_list_metrics","dynatrace_list_problem_comments","dynatrace_list_problems","dynatrace_list_remediation_items","dynatrace_list_security_problems","dynatrace_list_settings_objects","dynatrace_list_settings_schemas","dynatrace_list_slos","dynatrace_list_synthetic_monitors","dynatrace_list_tags","dynatrace_mute_security_problem","dynatrace_mute_security_problems","dynatrace_query_metrics","dynatrace_search_logs","dynatrace_unmute_security_problem","dynatrace_unmute_security_problems","dynatrace_update_problem_comment","dynatrace_update_settings_object","dynatrace_update_slo","elasticsearch_bulk","elasticsearch_cluster_health","elasticsearch_cluster_stats","elasticsearch_count","elasticsearch_create_index","elasticsearch_delete_document","elasticsearch_delete_index","elasticsearch_get_document","elasticsearch_get_index","elasticsearch_index_document","elasticsearch_list_indices","elasticsearch_search","elasticsearch_update_document","elevenlabs_audio_isolation","elevenlabs_edit_voice_settings","elevenlabs_get_user","elevenlabs_get_voice","elevenlabs_get_voice_settings","elevenlabs_list_models","elevenlabs_list_voices","elevenlabs_sound_effects","elevenlabs_speech_to_speech","elevenlabs_tts","emailbison_attach_leads_to_campaign","emailbison_attach_tags_to_leads","emailbison_create_campaign","emailbison_create_lead","emailbison_create_tag","emailbison_get_lead","emailbison_list_campaigns","emailbison_list_leads","emailbison_list_replies","emailbison_list_tags","emailbison_update_campaign","emailbison_update_campaign_status","emailbison_update_lead","embeddings_cohere","embeddings_gemini","embeddings_mistral","embeddings_ollama","embeddings_openai","embeddings_openrouter","enrich_check_credits","enrich_company_funding","enrich_company_lookup","enrich_company_revenue","enrich_disposable_email_check","enrich_email_to_ip","enrich_email_to_person_lite","enrich_email_to_phone","enrich_email_to_profile","enrich_find_email","enrich_get_post_details","enrich_ip_to_company","enrich_linkedin_profile","enrich_linkedin_to_personal_email","enrich_linkedin_to_work_email","enrich_phone_finder","enrich_reverse_hash_lookup","enrich_sales_pointer_people","enrich_search_company","enrich_search_company_activities","enrich_search_company_employees","enrich_search_jobs","enrich_search_logo","enrich_search_people","enrich_search_people_activities","enrich_search_post_comments","enrich_search_post_comments_by_url","enrich_search_post_reactions","enrich_search_post_reactions_by_url","enrich_search_posts","enrich_search_similar_companies","enrich_verify_email","enrichment_run","enrow_find_email","enrow_verify_email","exa_agent","exa_answer","exa_find_similar_links","exa_get_contents","exa_search","extend_parser","extend_parser_v2","fathom_get_summary","fathom_get_transcript","fathom_list_meeting_types","fathom_list_meetings","fathom_list_team_members","fathom_list_teams","file_append","file_compress","file_create_folder","file_decompress","file_delete_folder","file_edit","file_fetch","file_get","file_get_content","file_list","file_manage_sharing","file_move","file_parser","file_parser_v2","file_parser_v3","file_read","file_restore_folder","file_search","file_update_folder","file_write","findymail_find_email_from_linkedin","findymail_find_email_from_name","findymail_find_emails_by_domain","findymail_find_employees","findymail_find_phone","findymail_get_company","findymail_get_credits","findymail_lookup_technologies","findymail_reverse_email_lookup","findymail_search_technologies","findymail_verify_email","firecrawl_agent","firecrawl_batch_scrape","firecrawl_batch_scrape_status","firecrawl_cancel_crawl","firecrawl_crawl","firecrawl_crawl_status","firecrawl_credit_usage","firecrawl_extract","firecrawl_extract_status","firecrawl_map","firecrawl_parse","firecrawl_scrape","firecrawl_search","fireflies_add_to_live_meeting","fireflies_create_bite","fireflies_delete_transcript","fireflies_get_transcript","fireflies_get_user","fireflies_list_bites","fireflies_list_contacts","fireflies_list_transcripts","fireflies_list_users","fireflies_upload_audio","flint_create_task","flint_generate_pages","flint_get_task","function_execute","gamma_check_status","gamma_generate","gamma_generate_from_template","gamma_list_folders","gamma_list_themes","github_add_assignees","github_add_assignees_v2","github_add_labels","github_add_labels_v2","github_cancel_workflow_run","github_cancel_workflow_run_v2","github_check_star","github_check_star_v2","github_close_issue","github_close_issue_v2","github_close_pr","github_close_pr_v2","github_comment","github_comment_v2","github_compare_commits","github_compare_commits_v2","github_create_branch","github_create_branch_v2","github_create_comment_reaction","github_create_comment_reaction_v2","github_create_file","github_create_file_v2","github_create_gist","github_create_gist_v2","github_create_issue","github_create_issue_reaction","github_create_issue_reaction_v2","github_create_issue_v2","github_create_milestone","github_create_milestone_v2","github_create_pr","github_create_pr_review","github_create_pr_review_v2","github_create_pr_v2","github_create_project","github_create_project_v2","github_create_release","github_create_release_v2","github_delete_branch","github_delete_branch_v2","github_delete_comment","github_delete_comment_reaction","github_delete_comment_reaction_v2","github_delete_comment_v2","github_delete_file","github_delete_file_v2","github_delete_gist","github_delete_gist_v2","github_delete_issue_reaction","github_delete_issue_reaction_v2","github_delete_milestone","github_delete_milestone_v2","github_delete_project","github_delete_project_v2","github_delete_release","github_delete_release_v2","github_fork_gist","github_fork_gist_v2","github_fork_repo","github_fork_repo_v2","github_get_branch","github_get_branch_protection","github_get_branch_protection_v2","github_get_branch_v2","github_get_commit","github_get_commit_v2","github_get_file_content","github_get_file_content_v2","github_get_gist","github_get_gist_v2","github_get_issue","github_get_issue_v2","github_get_latest_release","github_get_latest_release_v2","github_get_milestone","github_get_milestone_v2","github_get_pr_files","github_get_pr_files_v2","github_get_project","github_get_project_v2","github_get_readme","github_get_readme_v2","github_get_release","github_get_release_v2","github_get_tree","github_get_tree_v2","github_get_workflow","github_get_workflow_run","github_get_workflow_run_v2","github_get_workflow_v2","github_issue_comment","github_issue_comment_v2","github_job_logs","github_latest_commit","github_latest_commit_v2","github_list_branches","github_list_branches_v2","github_list_commits","github_list_commits_v2","github_list_forks","github_list_forks_v2","github_list_gists","github_list_gists_v2","github_list_issue_comments","github_list_issue_comments_v2","github_list_issues","github_list_issues_v2","github_list_milestones","github_list_milestones_v2","github_list_pr_comments","github_list_pr_comments_v2","github_list_projects","github_list_projects_v2","github_list_prs","github_list_prs_v2","github_list_releases","github_list_releases_v2","github_list_review_threads","github_list_stargazers","github_list_stargazers_v2","github_list_tags","github_list_tags_v2","github_list_workflow_runs","github_list_workflow_runs_v2","github_list_workflows","github_list_workflows_v2","github_merge_pr","github_merge_pr_v2","github_pr","github_pr_v2","github_remove_label","github_remove_label_v2","github_reply_review_thread","github_repo_info","github_repo_info_v2","github_request_reviewers","github_request_reviewers_v2","github_rerun_workflow","github_rerun_workflow_v2","github_resolve_review_thread","github_search_code","github_search_code_v2","github_search_commits","github_search_commits_v2","github_search_issues","github_search_issues_v2","github_search_repos","github_search_repos_v2","github_search_users","github_search_users_v2","github_star_gist","github_star_gist_v2","github_star_repo","github_star_repo_v2","github_status_check_rollup","github_trigger_workflow","github_trigger_workflow_v2","github_unstar_gist","github_unstar_gist_v2","github_unstar_repo","github_unstar_repo_v2","github_update_branch_protection","github_update_branch_protection_v2","github_update_comment","github_update_comment_v2","github_update_file","github_update_file_v2","github_update_gist","github_update_gist_v2","github_update_issue","github_update_issue_v2","github_update_milestone","github_update_milestone_v2","github_update_pr","github_update_pr_v2","github_update_project","github_update_project_v2","github_update_release","github_update_release_v2","gitlab_activate_user","gitlab_add_member","gitlab_add_saml_group_link","gitlab_approve_access_request","gitlab_approve_merge_request","gitlab_approve_user","gitlab_ban_user","gitlab_block_user","gitlab_cancel_pipeline","gitlab_compare_branches","gitlab_create_branch","gitlab_create_file","gitlab_create_issue","gitlab_create_issue_note","gitlab_create_merge_request","gitlab_create_merge_request_note","gitlab_create_pipeline","gitlab_create_release","gitlab_create_user","gitlab_deactivate_user","gitlab_delete_branch","gitlab_delete_issue","gitlab_delete_saml_group_link","gitlab_delete_user","gitlab_delete_user_identity","gitlab_deny_access_request","gitlab_get_file","gitlab_get_group","gitlab_get_issue","gitlab_get_job_log","gitlab_get_merge_request","gitlab_get_merge_request_changes","gitlab_get_pipeline","gitlab_get_project","gitlab_invite_member","gitlab_list_access_requests","gitlab_list_branches","gitlab_list_commits","gitlab_list_groups","gitlab_list_invitations","gitlab_list_issues","gitlab_list_members","gitlab_list_merge_requests","gitlab_list_pipeline_jobs","gitlab_list_pipelines","gitlab_list_projects","gitlab_list_releases","gitlab_list_repository_tree","gitlab_list_saml_group_links","gitlab_list_user_memberships","gitlab_merge_merge_request","gitlab_play_job","gitlab_reject_user","gitlab_remove_member","gitlab_retry_pipeline","gitlab_revoke_invitation","gitlab_search_users","gitlab_unban_user","gitlab_unblock_user","gitlab_update_file","gitlab_update_invitation","gitlab_update_issue","gitlab_update_member","gitlab_update_merge_request","gitlab_update_user","gmail_add_label","gmail_add_label_v2","gmail_archive","gmail_archive_v2","gmail_create_label_v2","gmail_delete","gmail_delete_draft_v2","gmail_delete_label_v2","gmail_delete_v2","gmail_draft","gmail_draft_v2","gmail_edit_draft_v2","gmail_get_draft_v2","gmail_get_thread_v2","gmail_list_drafts_v2","gmail_list_labels_v2","gmail_list_threads_v2","gmail_mark_read","gmail_mark_read_v2","gmail_mark_unread","gmail_mark_unread_v2","gmail_move","gmail_move_v2","gmail_read","gmail_read_v2","gmail_remove_label","gmail_remove_label_v2","gmail_search","gmail_search_v2","gmail_send","gmail_send_v2","gmail_trash_thread_v2","gmail_unarchive","gmail_unarchive_v2","gmail_untrash_thread_v2","gmail_update_label_v2","gong_aggregate_activity","gong_aggregate_by_period","gong_answered_scorecards","gong_ask_anything","gong_assign_flow_prospects","gong_create_call","gong_day_by_day_activity","gong_get_brief","gong_get_call","gong_get_call_transcript","gong_get_coaching","gong_get_extensive_calls","gong_get_folder_content","gong_get_logs","gong_get_prospect_flows","gong_get_user","gong_interaction_stats","gong_list_calls","gong_list_flows","gong_list_library_folders","gong_list_scorecards","gong_list_trackers","gong_list_users","gong_list_workspaces","gong_lookup_email","gong_lookup_phone","gong_purge_email_address","gong_purge_phone_number","gong_unassign_flow_prospects","google_ads_ad_performance","google_ads_campaign_performance","google_ads_list_ad_groups","google_ads_list_campaigns","google_ads_list_customers","google_ads_search","google_appsheet_add_rows","google_appsheet_delete_rows","google_appsheet_edit_rows","google_appsheet_find_rows","google_bigquery_create_dataset","google_bigquery_create_table","google_bigquery_delete_dataset","google_bigquery_delete_table","google_bigquery_get_query_results","google_bigquery_get_table","google_bigquery_insert_rows","google_bigquery_list_datasets","google_bigquery_list_table_data","google_bigquery_list_tables","google_bigquery_query","google_books_volume_details","google_books_volume_search","google_calendar_create","google_calendar_create_calendar","google_calendar_create_calendar_v2","google_calendar_create_v2","google_calendar_delete","google_calendar_delete_calendar","google_calendar_delete_calendar_v2","google_calendar_delete_v2","google_calendar_freebusy","google_calendar_freebusy_v2","google_calendar_get","google_calendar_get_v2","google_calendar_instances","google_calendar_instances_v2","google_calendar_invite","google_calendar_invite_v2","google_calendar_list","google_calendar_list_acl","google_calendar_list_acl_v2","google_calendar_list_calendars","google_calendar_list_calendars_v2","google_calendar_list_v2","google_calendar_move","google_calendar_move_v2","google_calendar_quick_add","google_calendar_quick_add_v2","google_calendar_share_calendar","google_calendar_share_calendar_v2","google_calendar_unshare_calendar","google_calendar_unshare_calendar_v2","google_calendar_update","google_calendar_update_acl","google_calendar_update_acl_v2","google_calendar_update_calendar","google_calendar_update_calendar_v2","google_calendar_update_v2","google_contacts_create","google_contacts_delete","google_contacts_get","google_contacts_list","google_contacts_search","google_contacts_update","google_docs_create","google_docs_create_named_range","google_docs_create_paragraph_bullets","google_docs_delete_content_range","google_docs_delete_named_range","google_docs_delete_paragraph_bullets","google_docs_insert_image","google_docs_insert_page_break","google_docs_insert_table","google_docs_insert_text","google_docs_read","google_docs_replace_text","google_docs_update_paragraph_style","google_docs_update_text_style","google_docs_write","google_drive_copy","google_drive_create_comment","google_drive_create_folder","google_drive_delete","google_drive_delete_comment","google_drive_download","google_drive_export","google_drive_get_about","google_drive_get_content","google_drive_get_file","google_drive_get_revision","google_drive_list","google_drive_list_comments","google_drive_list_permissions","google_drive_list_revisions","google_drive_move","google_drive_search","google_drive_share","google_drive_trash","google_drive_unshare","google_drive_untrash","google_drive_update","google_drive_upload","google_forms_batch_update","google_forms_create_form","google_forms_create_watch","google_forms_delete_watch","google_forms_get_form","google_forms_get_responses","google_forms_list_watches","google_forms_renew_watch","google_forms_set_publish_settings","google_groups_add_alias","google_groups_add_member","google_groups_create_group","google_groups_delete_group","google_groups_get_group","google_groups_get_member","google_groups_get_settings","google_groups_has_member","google_groups_list_aliases","google_groups_list_groups","google_groups_list_members","google_groups_remove_alias","google_groups_remove_member","google_groups_update_group","google_groups_update_member","google_groups_update_settings","google_maps_air_quality","google_maps_directions","google_maps_distance_matrix","google_maps_elevation","google_maps_geocode","google_maps_geolocate","google_maps_place_details","google_maps_places_nearby","google_maps_places_search","google_maps_pollen","google_maps_reverse_geocode","google_maps_snap_to_roads","google_maps_solar","google_maps_speed_limits","google_maps_timezone","google_maps_validate_address","google_meet_create_space","google_meet_end_conference","google_meet_get_conference_record","google_meet_get_space","google_meet_list_conference_records","google_meet_list_participants","google_pagespeed_analyze","google_search","google_sheets_append","google_sheets_append_v2","google_sheets_batch_clear_v2","google_sheets_batch_get_v2","google_sheets_batch_update_v2","google_sheets_clear_v2","google_sheets_copy_sheet_v2","google_sheets_create_spreadsheet_v2","google_sheets_delete_rows_v2","google_sheets_delete_sheet_v2","google_sheets_delete_spreadsheet_v2","google_sheets_get_spreadsheet_v2","google_sheets_read","google_sheets_read_v2","google_sheets_update","google_sheets_update_v2","google_sheets_write","google_sheets_write_v2","google_slides_add_image","google_slides_add_slide","google_slides_batch_update","google_slides_copy_presentation","google_slides_create","google_slides_create_line","google_slides_create_paragraph_bullets","google_slides_create_shape","google_slides_create_sheets_chart","google_slides_create_table","google_slides_create_video","google_slides_delete_object","google_slides_delete_paragraph_bullets","google_slides_delete_table_column","google_slides_delete_table_row","google_slides_delete_text","google_slides_duplicate_object","google_slides_export_presentation","google_slides_get_page","google_slides_get_thumbnail","google_slides_group_objects","google_slides_insert_table_columns","google_slides_insert_table_rows","google_slides_insert_text","google_slides_merge_table_cells","google_slides_read","google_slides_refresh_sheets_chart","google_slides_replace_all_shapes_with_image","google_slides_replace_all_shapes_with_sheets_chart","google_slides_replace_all_text","google_slides_replace_image","google_slides_reroute_line","google_slides_ungroup_objects","google_slides_unmerge_table_cells","google_slides_update_image_properties","google_slides_update_line_category","google_slides_update_line_properties","google_slides_update_page_element_alt_text","google_slides_update_page_element_transform","google_slides_update_page_elements_z_order","google_slides_update_page_properties","google_slides_update_paragraph_style","google_slides_update_shape_properties","google_slides_update_slide_properties","google_slides_update_slides_position","google_slides_update_table_border_properties","google_slides_update_table_cell_properties","google_slides_update_table_column_properties","google_slides_update_table_row_properties","google_slides_update_text_style","google_slides_update_video_properties","google_slides_write","google_tasks_create","google_tasks_delete","google_tasks_get","google_tasks_list","google_tasks_list_task_lists","google_tasks_update","google_translate_detect","google_translate_text","google_vault_add_held_accounts","google_vault_add_matters_permissions","google_vault_close_matters","google_vault_create_matters","google_vault_create_matters_export","google_vault_create_matters_holds","google_vault_create_saved_query","google_vault_delete_matters","google_vault_delete_matters_export","google_vault_delete_matters_holds","google_vault_delete_saved_query","google_vault_download_export_file","google_vault_list_matters","google_vault_list_matters_export","google_vault_list_matters_holds","google_vault_list_saved_queries","google_vault_remove_held_accounts","google_vault_remove_matters_permissions","google_vault_reopen_matters","google_vault_undelete_matters","google_vault_update_matters","google_vault_update_matters_holds","grafana_check_data_source_health","grafana_create_alert_rule","grafana_create_annotation","grafana_create_contact_point","grafana_create_dashboard","grafana_create_folder","grafana_delete_alert_rule","grafana_delete_annotation","grafana_delete_contact_point","grafana_delete_dashboard","grafana_delete_folder","grafana_get_alert_rule","grafana_get_alert_rule_group","grafana_get_dashboard","grafana_get_data_source","grafana_get_folder","grafana_get_health","grafana_list_alert_rules","grafana_list_annotations","grafana_list_contact_points","grafana_list_dashboards","grafana_list_data_sources","grafana_list_folders","grafana_move_folder","grafana_query_data_source","grafana_update_alert_rule","grafana_update_annotation","grafana_update_contact_point","grafana_update_dashboard","grafana_update_folder","grain_create_hook","grain_create_hook_v2","grain_delete_hook","grain_delete_hook_v2","grain_get_recording","grain_get_transcript","grain_list_hooks","grain_list_hooks_v2","grain_list_meeting_types","grain_list_recordings","grain_list_teams","grain_list_views","granola_create_webhook_endpoint","granola_delete_webhook_endpoint","granola_get_note","granola_get_transcript","granola_list_audit_events","granola_list_folders","granola_list_notes","granola_list_webhook_endpoints","granola_update_webhook_endpoint","greenhouse_get_application","greenhouse_get_candidate","greenhouse_get_job","greenhouse_get_user","greenhouse_list_applications","greenhouse_list_candidates","greenhouse_list_departments","greenhouse_list_job_stages","greenhouse_list_jobs","greenhouse_list_offices","greenhouse_list_users","greptile_index_repo","greptile_query","greptile_search","greptile_status","guardrails_validate","harmonic_batch_get_people","harmonic_clear_people_saved_search_net_new_results","harmonic_enrich_person","harmonic_get_company_employees","harmonic_get_email_enrichment_job","harmonic_get_email_enrichment_usage","harmonic_get_enrichment_status","harmonic_get_people_saved_search_net_new_results","harmonic_get_people_saved_search_results","harmonic_get_person","harmonic_list_people_saved_searches","harmonic_search_people_scout","harmonic_submit_email_enrichment_job","hex_cancel_run","hex_create_collection","hex_create_group","hex_deactivate_user","hex_delete_group","hex_get_collection","hex_get_data_connection","hex_get_group","hex_get_project","hex_get_project_runs","hex_get_queried_tables","hex_get_run_status","hex_list_collections","hex_list_data_connections","hex_list_groups","hex_list_projects","hex_list_users","hex_run_project","hex_update_collection","hex_update_group","hex_update_project","http_request","hubspot_add_list_memberships","hubspot_create_appointment","hubspot_create_association","hubspot_create_company","hubspot_create_contact","hubspot_create_deal","hubspot_create_email","hubspot_create_line_item","hubspot_create_list","hubspot_create_note","hubspot_create_ticket","hubspot_delete_association","hubspot_delete_company","hubspot_delete_contact","hubspot_delete_deal","hubspot_delete_line_item","hubspot_delete_ticket","hubspot_get_appointment","hubspot_get_association_labels","hubspot_get_cart","hubspot_get_company","hubspot_get_contact","hubspot_get_deal","hubspot_get_email","hubspot_get_line_item","hubspot_get_list","hubspot_get_list_memberships","hubspot_get_marketing_event","hubspot_get_note","hubspot_get_properties","hubspot_get_quote","hubspot_get_ticket","hubspot_get_users","hubspot_list_appointments","hubspot_list_associations","hubspot_list_carts","hubspot_list_companies","hubspot_list_contacts","hubspot_list_deals","hubspot_list_emails","hubspot_list_line_items","hubspot_list_lists","hubspot_list_marketing_events","hubspot_list_notes","hubspot_list_owners","hubspot_list_quotes","hubspot_list_tickets","hubspot_remove_list_memberships","hubspot_search_companies","hubspot_search_contacts","hubspot_search_deals","hubspot_search_emails","hubspot_search_line_items","hubspot_search_notes","hubspot_search_quotes","hubspot_search_tickets","hubspot_update_appointment","hubspot_update_company","hubspot_update_contact","hubspot_update_deal","hubspot_update_line_item","hubspot_update_ticket","huggingface_chat","hunter_companies_find","hunter_discover","hunter_domain_search","hunter_email_count","hunter_email_finder","hunter_email_verifier","iam_add_user_to_group","iam_attach_role_policy","iam_attach_user_policy","iam_create_access_key","iam_create_role","iam_create_user","iam_delete_access_key","iam_delete_role","iam_delete_user","iam_detach_role_policy","iam_detach_user_policy","iam_get_policy","iam_get_role","iam_get_user","iam_list_access_keys","iam_list_attached_role_policies","iam_list_attached_user_policies","iam_list_groups","iam_list_policies","iam_list_roles","iam_list_users","iam_remove_user_from_group","iam_simulate_principal_policy","iam_update_access_key","icypeas_find_email","icypeas_verify_email","identity_center_check_assignment_deletion_status","identity_center_check_assignment_status","identity_center_create_account_assignment","identity_center_delete_account_assignment","identity_center_describe_account","identity_center_describe_group","identity_center_describe_user","identity_center_get_group","identity_center_get_user","identity_center_list_account_assignments","identity_center_list_accounts","identity_center_list_assignments_for_account","identity_center_list_group_memberships","identity_center_list_groups","identity_center_list_instances","identity_center_list_permission_sets","image_generate","incidentio_actions_create","incidentio_actions_list","incidentio_actions_show","incidentio_actions_update","incidentio_alert_events_create","incidentio_alerts_list","incidentio_alerts_resolve","incidentio_alerts_show","incidentio_catalog_entries_list","incidentio_catalog_types_list","incidentio_custom_fields_create","incidentio_custom_fields_delete","incidentio_custom_fields_list","incidentio_custom_fields_show","incidentio_custom_fields_update","incidentio_escalation_paths_create","incidentio_escalation_paths_delete","incidentio_escalation_paths_list","incidentio_escalation_paths_show","incidentio_escalation_paths_update","incidentio_escalations_cancel","incidentio_escalations_create","incidentio_escalations_list","incidentio_escalations_show","incidentio_follow_ups_create","incidentio_follow_ups_list","incidentio_follow_ups_show","incidentio_follow_ups_update","incidentio_incident_alerts_list","incidentio_incident_memberships_create","incidentio_incident_memberships_revoke","incidentio_incident_participants_list","incidentio_incident_roles_create","incidentio_incident_roles_delete","incidentio_incident_roles_list","incidentio_incident_roles_show","incidentio_incident_roles_update","incidentio_incident_statuses_list","incidentio_incident_timestamps_list","incidentio_incident_timestamps_show","incidentio_incident_types_list","incidentio_incident_updates_list","incidentio_incidents_create","incidentio_incidents_list","incidentio_incidents_show","incidentio_incidents_update","incidentio_on_call_now","incidentio_schedule_entries_list","incidentio_schedule_overrides_create","incidentio_schedule_overrides_list","incidentio_schedules_create","incidentio_schedules_delete","incidentio_schedules_list","incidentio_schedules_show","incidentio_schedules_update","incidentio_severities_list","incidentio_teams_list","incidentio_teams_show","incidentio_users_list","incidentio_users_show","incidentio_workflows_create","incidentio_workflows_delete","incidentio_workflows_list","incidentio_workflows_show","incidentio_workflows_update","infisical_create_secret","infisical_delete_secret","infisical_get_secret","infisical_list_secrets","infisical_update_secret","instagram_delete_comment","instagram_download_media","instagram_get_account_insights","instagram_get_container_status","instagram_get_conversation_messages","instagram_get_media","instagram_get_media_insights","instagram_get_message","instagram_get_profile","instagram_get_publishing_limit","instagram_hide_comment","instagram_list_comments","instagram_list_conversations","instagram_list_media","instagram_list_stories","instagram_private_reply","instagram_publish_carousel","instagram_publish_image","instagram_publish_reel","instagram_publish_story","instagram_publish_video","instagram_reply_to_comment","instagram_send_text_message","instagram_set_comments_enabled","instantly_activate_campaign","instantly_create_campaign","instantly_create_lead","instantly_create_lead_list","instantly_delete_campaign","instantly_delete_leads","instantly_get_lead","instantly_list_campaigns","instantly_list_emails","instantly_list_lead_lists","instantly_list_leads","instantly_patch_campaign","instantly_patch_lead","instantly_pause_campaign","instantly_reply_to_email","instantly_update_lead_interest_status","intercom_assign_conversation_v2","intercom_attach_contact_to_company_v2","intercom_close_conversation_v2","intercom_create_company","intercom_create_company_v2","intercom_create_contact","intercom_create_contact_v2","intercom_create_event_v2","intercom_create_message","intercom_create_message_v2","intercom_create_note_v2","intercom_create_tag_v2","intercom_create_ticket","intercom_create_ticket_v2","intercom_delete_contact","intercom_delete_contact_v2","intercom_detach_contact_from_company_v2","intercom_get_company","intercom_get_company_v2","intercom_get_contact","intercom_get_contact_v2","intercom_get_conversation","intercom_get_conversation_v2","intercom_get_ticket","intercom_get_ticket_v2","intercom_list_admins_v2","intercom_list_companies","intercom_list_companies_v2","intercom_list_contacts","intercom_list_contacts_v2","intercom_list_conversations","intercom_list_conversations_v2","intercom_list_tags_v2","intercom_open_conversation_v2","intercom_reply_conversation","intercom_reply_conversation_v2","intercom_search_contacts","intercom_search_contacts_v2","intercom_search_conversations","intercom_search_conversations_v2","intercom_snooze_conversation_v2","intercom_tag_contact_v2","intercom_tag_conversation_v2","intercom_untag_contact_v2","intercom_update_contact","intercom_update_contact_v2","intercom_update_ticket_v2","jina_read_url","jina_search","jira_add_attachment","jira_add_comment","jira_add_watcher","jira_add_worklog","jira_assign_issue","jira_bulk_read","jira_create_issue_link","jira_delete_attachment","jira_delete_comment","jira_delete_issue","jira_delete_issue_link","jira_delete_worklog","jira_get_attachments","jira_get_comments","jira_get_fields","jira_get_project","jira_get_transitions","jira_get_users","jira_get_worklogs","jira_list_issue_types","jira_list_projects","jira_remove_watcher","jira_retrieve","jira_search_issues","jira_search_users","jira_transition_issue","jira_update","jira_update_comment","jira_update_worklog","jira_write","jotform_add_label_resources","jotform_clone_form","jotform_create_form","jotform_create_label","jotform_create_question","jotform_create_questions","jotform_create_report","jotform_create_submission","jotform_create_submissions","jotform_create_webhook","jotform_delete_form","jotform_delete_label","jotform_delete_question","jotform_delete_report","jotform_delete_submission","jotform_delete_webhook","jotform_get_form","jotform_get_form_properties","jotform_get_history","jotform_get_label","jotform_get_question","jotform_get_report","jotform_get_settings","jotform_get_submission","jotform_get_usage","jotform_get_user","jotform_list_form_files","jotform_list_form_reports","jotform_list_form_submissions","jotform_list_forms","jotform_list_label_resources","jotform_list_labels","jotform_list_questions","jotform_list_reports","jotform_list_submissions","jotform_list_subusers","jotform_list_webhooks","jotform_remove_label_resources","jotform_update_form_properties","jotform_update_label","jotform_update_question","jotform_update_settings","jotform_update_submission","jsm_add_comment","jsm_add_customer","jsm_add_organization","jsm_add_participants","jsm_answer_approval","jsm_attach_form","jsm_copy_forms","jsm_create_object","jsm_create_organization","jsm_create_request","jsm_delete_form","jsm_delete_object","jsm_externalise_form","jsm_get_approvals","jsm_get_comments","jsm_get_customers","jsm_get_form","jsm_get_form_answers","jsm_get_form_structure","jsm_get_form_templates","jsm_get_issue_forms","jsm_get_object","jsm_get_object_schema","jsm_get_object_type_attributes","jsm_get_organizations","jsm_get_participants","jsm_get_queues","jsm_get_request","jsm_get_request_type_fields","jsm_get_request_types","jsm_get_requests","jsm_get_service_desks","jsm_get_sla","jsm_get_transitions","jsm_internalise_form","jsm_list_object_schemas","jsm_list_object_types","jsm_reopen_form","jsm_save_form_answers","jsm_search_objects_aql","jsm_submit_form","jsm_transition_request","jsm_update_object","jupyter_copy_content","jupyter_create_file","jupyter_create_session","jupyter_delete_content","jupyter_delete_session","jupyter_get_content","jupyter_get_content_v2","jupyter_interrupt_kernel","jupyter_list_contents","jupyter_list_kernels","jupyter_list_kernelspecs","jupyter_list_sessions","jupyter_rename_content","jupyter_restart_kernel","jupyter_start_kernel","jupyter_stop_kernel","jupyter_upload_file","kalshi_amend_order","kalshi_amend_order_v2","kalshi_cancel_order","kalshi_cancel_order_v2","kalshi_create_order","kalshi_create_order_v2","kalshi_get_balance","kalshi_get_balance_v2","kalshi_get_candlesticks","kalshi_get_candlesticks_v2","kalshi_get_event","kalshi_get_event_candlesticks","kalshi_get_event_candlesticks_v2","kalshi_get_event_v2","kalshi_get_events","kalshi_get_events_v2","kalshi_get_exchange_announcements","kalshi_get_exchange_announcements_v2","kalshi_get_exchange_schedule","kalshi_get_exchange_schedule_v2","kalshi_get_exchange_status","kalshi_get_exchange_status_v2","kalshi_get_fills","kalshi_get_fills_v2","kalshi_get_market","kalshi_get_market_v2","kalshi_get_markets","kalshi_get_markets_v2","kalshi_get_order","kalshi_get_order_v2","kalshi_get_orderbook","kalshi_get_orderbook_v2","kalshi_get_orders","kalshi_get_orders_v2","kalshi_get_positions","kalshi_get_positions_v2","kalshi_get_series_by_ticker","kalshi_get_series_by_ticker_v2","kalshi_get_series_list","kalshi_get_series_list_v2","kalshi_get_settlements","kalshi_get_settlements_v2","kalshi_get_trades","kalshi_get_trades_v2","ketch_get_consent","ketch_get_subscriptions","ketch_invoke_right","ketch_set_consent","ketch_set_subscriptions","knowledge_create_document","knowledge_delete_chunk","knowledge_delete_document","knowledge_get_connector","knowledge_get_document","knowledge_list_chunks","knowledge_list_connectors","knowledge_list_documents","knowledge_list_tags","knowledge_search","knowledge_trigger_sync","knowledge_update_chunk","knowledge_upload_chunk","knowledge_upsert_document","lambda_add_permission","lambda_create_alias","lambda_create_event_source_mapping","lambda_create_function","lambda_create_function_url_config","lambda_delete_alias","lambda_delete_event_source_mapping","lambda_delete_function","lambda_delete_function_concurrency","lambda_delete_function_event_invoke_config","lambda_delete_function_url_config","lambda_delete_provisioned_concurrency_config","lambda_get_account_settings","lambda_get_alias","lambda_get_event_source_mapping","lambda_get_function","lambda_get_function_concurrency","lambda_get_function_configuration","lambda_get_function_event_invoke_config","lambda_get_function_recursion_config","lambda_get_function_url_config","lambda_get_layer_version","lambda_get_policy","lambda_get_provisioned_concurrency_config","lambda_get_runtime_management_config","lambda_invoke","lambda_list_aliases","lambda_list_event_source_mappings","lambda_list_function_event_invoke_configs","lambda_list_function_url_configs","lambda_list_functions","lambda_list_layer_versions","lambda_list_layers","lambda_list_provisioned_concurrency_configs","lambda_list_tags","lambda_list_versions_by_function","lambda_publish_version","lambda_put_function_concurrency","lambda_put_function_event_invoke_config","lambda_put_function_recursion_config","lambda_put_provisioned_concurrency_config","lambda_put_runtime_management_config","lambda_remove_permission","lambda_tag_resource","lambda_untag_resource","lambda_update_alias","lambda_update_event_source_mapping","lambda_update_function_code","lambda_update_function_configuration","lambda_update_function_url_config","langsmith_create_feedback","langsmith_create_run","langsmith_create_runs_batch","langsmith_get_run","langsmith_update_run","latex_compile","latex_get_package","latex_list_fonts","latex_search_packages","launchdarkly_create_flag","launchdarkly_delete_flag","launchdarkly_get_audit_log","launchdarkly_get_flag","launchdarkly_get_flag_status","launchdarkly_list_environments","launchdarkly_list_flags","launchdarkly_list_members","launchdarkly_list_projects","launchdarkly_list_segments","launchdarkly_toggle_flag","launchdarkly_update_flag","leadmagic_company_search","leadmagic_email_to_profile","leadmagic_find_email","leadmagic_find_mobile","leadmagic_get_credits","leadmagic_profile_search","leadmagic_profile_to_email","leadmagic_role_finder","leadmagic_validate_email","lemlist_get_activities","lemlist_get_lead","lemlist_send_email","linear_add_label_to_issue","linear_add_label_to_project","linear_archive_issue","linear_archive_label","linear_archive_project","linear_create_attachment","linear_create_comment","linear_create_customer","linear_create_customer_request","linear_create_customer_status","linear_create_customer_tier","linear_create_cycle","linear_create_favorite","linear_create_issue","linear_create_issue_relation","linear_create_label","linear_create_project","linear_create_project_label","linear_create_project_milestone","linear_create_project_status","linear_create_project_update","linear_create_workflow_state","linear_delete_attachment","linear_delete_comment","linear_delete_customer","linear_delete_customer_status","linear_delete_customer_tier","linear_delete_issue","linear_delete_issue_relation","linear_delete_project","linear_delete_project_label","linear_delete_project_milestone","linear_delete_project_status","linear_get_active_cycle","linear_get_customer","linear_get_cycle","linear_get_issue","linear_get_project","linear_get_viewer","linear_list_attachments","linear_list_comments","linear_list_customer_requests","linear_list_customer_statuses","linear_list_customer_tiers","linear_list_customers","linear_list_cycles","linear_list_favorites","linear_list_issue_relations","linear_list_labels","linear_list_notifications","linear_list_project_labels","linear_list_project_milestones","linear_list_project_statuses","linear_list_project_updates","linear_list_projects","linear_list_teams","linear_list_users","linear_list_workflow_states","linear_merge_customers","linear_read_issues","linear_remove_label_from_issue","linear_remove_label_from_project","linear_search_issues","linear_unarchive_issue","linear_update_attachment","linear_update_comment","linear_update_customer","linear_update_customer_request","linear_update_customer_status","linear_update_customer_tier","linear_update_issue","linear_update_label","linear_update_notification","linear_update_project","linear_update_project_label","linear_update_project_milestone","linear_update_project_status","linear_update_workflow_state","linkedin_get_profile","linkedin_share_post","linkup_search","linq_add_participant","linq_check_imessage","linq_check_rcs","linq_create_attachment","linq_create_chat","linq_create_contact_card","linq_create_webhook_subscription","linq_delete_attachment","linq_delete_message","linq_delete_webhook_subscription","linq_edit_message","linq_get_attachment","linq_get_chat","linq_get_contact_card","linq_get_message","linq_get_webhook_subscription","linq_leave_chat","linq_list_chats","linq_list_messages","linq_list_phone_numbers","linq_list_thread","linq_list_webhook_events","linq_list_webhook_subscriptions","linq_mark_chat_read","linq_react_to_message","linq_remove_participant","linq_send_message","linq_send_voice_memo","linq_share_contact_card","linq_start_typing","linq_stop_typing","linq_update_chat","linq_update_contact_card","linq_update_webhook_subscription","llm_chat","logfire_get_token_info","logfire_get_trace","logfire_query","logfire_search_records","logrocket_create_release","logrocket_get_audit_logs","logrocket_get_highlights","logrocket_identify_user","logrocket_list_exported_sessions","logrocket_request_highlights","logs_get","logs_get_execution","logs_get_run_details","logs_query","logs_query_runs","loops_check_contact_suppression","loops_create_contact","loops_create_contact_property","loops_delete_contact","loops_find_contact","loops_get_transactional_email","loops_list_contact_properties","loops_list_mailing_lists","loops_list_transactional_emails","loops_remove_contact_suppression","loops_send_event","loops_send_transactional_email","loops_update_contact","luma_add_guests","luma_cancel_event","luma_create_event","luma_get_event","luma_get_guest","luma_get_guests","luma_list_events","luma_lookup_event","luma_send_invites","luma_update_event","luma_update_guest_status","mailchimp_add_member","mailchimp_add_member_tags","mailchimp_add_or_update_member","mailchimp_add_segment_member","mailchimp_add_subscriber_to_automation","mailchimp_archive_member","mailchimp_create_audience","mailchimp_create_batch_operation","mailchimp_create_campaign","mailchimp_create_interest","mailchimp_create_interest_category","mailchimp_create_landing_page","mailchimp_create_merge_field","mailchimp_create_segment","mailchimp_create_template","mailchimp_delete_audience","mailchimp_delete_batch_operation","mailchimp_delete_campaign","mailchimp_delete_interest","mailchimp_delete_interest_category","mailchimp_delete_landing_page","mailchimp_delete_member","mailchimp_delete_merge_field","mailchimp_delete_segment","mailchimp_delete_template","mailchimp_get_audience","mailchimp_get_audiences","mailchimp_get_automation","mailchimp_get_automations","mailchimp_get_batch_operation","mailchimp_get_batch_operations","mailchimp_get_campaign","mailchimp_get_campaign_content","mailchimp_get_campaign_report","mailchimp_get_campaign_reports","mailchimp_get_campaigns","mailchimp_get_interest","mailchimp_get_interest_categories","mailchimp_get_interest_category","mailchimp_get_interests","mailchimp_get_landing_page","mailchimp_get_landing_pages","mailchimp_get_member","mailchimp_get_member_tags","mailchimp_get_members","mailchimp_get_merge_field","mailchimp_get_merge_fields","mailchimp_get_segment","mailchimp_get_segment_members","mailchimp_get_segments","mailchimp_get_template","mailchimp_get_templates","mailchimp_pause_automation","mailchimp_publish_landing_page","mailchimp_remove_member_tags","mailchimp_remove_segment_member","mailchimp_replicate_campaign","mailchimp_schedule_campaign","mailchimp_send_campaign","mailchimp_set_campaign_content","mailchimp_start_automation","mailchimp_unarchive_member","mailchimp_unpublish_landing_page","mailchimp_unschedule_campaign","mailchimp_update_audience","mailchimp_update_campaign","mailchimp_update_interest","mailchimp_update_interest_category","mailchimp_update_landing_page","mailchimp_update_member","mailchimp_update_merge_field","mailchimp_update_segment","mailchimp_update_template","mailgun_add_list_member","mailgun_create_mailing_list","mailgun_get_domain","mailgun_get_mailing_list","mailgun_get_message","mailgun_list_domains","mailgun_list_messages","mailgun_send_message","managed_agent_archive_session","managed_agent_create_session","managed_agent_delete_session","managed_agent_get_session","managed_agent_interrupt_session","managed_agent_list_events","managed_agent_respond_custom_tool","managed_agent_respond_tool_confirmation","managed_agent_run_session","managed_agent_send_message","managed_agent_update_session","manageengine_sdp_add_change_note","manageengine_sdp_add_problem_note","manageengine_sdp_add_request_note","manageengine_sdp_create_asset","manageengine_sdp_create_change","manageengine_sdp_create_problem","manageengine_sdp_create_request","manageengine_sdp_create_solution","manageengine_sdp_delete_asset","manageengine_sdp_delete_change","manageengine_sdp_delete_problem","manageengine_sdp_delete_request","manageengine_sdp_delete_solution","manageengine_sdp_get_asset","manageengine_sdp_get_change","manageengine_sdp_get_problem","manageengine_sdp_get_request","manageengine_sdp_get_solution","manageengine_sdp_list_assets","manageengine_sdp_list_change_notes","manageengine_sdp_list_changes","manageengine_sdp_list_problem_notes","manageengine_sdp_list_problems","manageengine_sdp_list_request_notes","manageengine_sdp_list_requests","manageengine_sdp_list_solutions","manageengine_sdp_update_asset","manageengine_sdp_update_change","manageengine_sdp_update_problem","manageengine_sdp_update_request","manageengine_sdp_update_solution","mcp_list_operations","mcp_run_operation","mem0_add_memories","mem0_get_memories","mem0_search_memories","memory_add","memory_delete","memory_get","memory_get_all","microsoft_ad_add_directory_role_member","microsoft_ad_add_group_member","microsoft_ad_add_user_app_role_assignment","microsoft_ad_assign_license","microsoft_ad_create_group","microsoft_ad_create_user","microsoft_ad_delete_group","microsoft_ad_delete_user","microsoft_ad_get_conditional_access_policy","microsoft_ad_get_device","microsoft_ad_get_group","microsoft_ad_get_user","microsoft_ad_list_authentication_methods","microsoft_ad_list_conditional_access_policies","microsoft_ad_list_devices","microsoft_ad_list_directory_audits","microsoft_ad_list_directory_role_members","microsoft_ad_list_directory_roles","microsoft_ad_list_group_members","microsoft_ad_list_groups","microsoft_ad_list_service_principal_app_role_assignments","microsoft_ad_list_service_principals","microsoft_ad_list_sign_ins","microsoft_ad_list_subscribed_skus","microsoft_ad_list_user_app_role_assignments","microsoft_ad_list_user_devices","microsoft_ad_list_user_licenses","microsoft_ad_list_users","microsoft_ad_remove_directory_role_member","microsoft_ad_remove_group_member","microsoft_ad_remove_user_app_role_assignment","microsoft_ad_reset_password","microsoft_ad_revoke_sign_in_sessions","microsoft_ad_set_password","microsoft_ad_update_group","microsoft_ad_update_user","microsoft_dataverse_associate","microsoft_dataverse_create_multiple","microsoft_dataverse_create_record","microsoft_dataverse_delete_record","microsoft_dataverse_disassociate","microsoft_dataverse_download_file","microsoft_dataverse_download_file_v2","microsoft_dataverse_execute_action","microsoft_dataverse_execute_function","microsoft_dataverse_fetchxml_query","microsoft_dataverse_get_entity_metadata","microsoft_dataverse_get_record","microsoft_dataverse_list_records","microsoft_dataverse_search","microsoft_dataverse_update_multiple","microsoft_dataverse_update_record","microsoft_dataverse_upload_file","microsoft_dataverse_upsert_record","microsoft_dataverse_whoami","microsoft_dynamics_365_close_case","microsoft_dynamics_365_close_opportunity","microsoft_dynamics_365_create_record","microsoft_dynamics_365_get_record","microsoft_dynamics_365_list_records","microsoft_dynamics_365_qualify_lead","microsoft_dynamics_365_search_records","microsoft_dynamics_365_update_record","microsoft_excel_clear_range","microsoft_excel_create_table","microsoft_excel_delete_worksheet","microsoft_excel_format_range","microsoft_excel_read","microsoft_excel_read_v2","microsoft_excel_sort_range","microsoft_excel_table_add","microsoft_excel_worksheet_add","microsoft_excel_write","microsoft_excel_write_v2","microsoft_planner_create_bucket","microsoft_planner_create_plan","microsoft_planner_create_task","microsoft_planner_delete_bucket","microsoft_planner_delete_plan","microsoft_planner_delete_task","microsoft_planner_get_plan_details","microsoft_planner_get_task_details","microsoft_planner_list_buckets","microsoft_planner_list_plans","microsoft_planner_read_bucket","microsoft_planner_read_plan","microsoft_planner_read_task","microsoft_planner_update_bucket","microsoft_planner_update_plan","microsoft_planner_update_plan_details","microsoft_planner_update_task","microsoft_planner_update_task_details","microsoft_teams_delete_channel_message","microsoft_teams_delete_chat_message","microsoft_teams_get_message","microsoft_teams_list_channel_members","microsoft_teams_list_channels","microsoft_teams_list_chat_members","microsoft_teams_list_chats","microsoft_teams_list_team_members","microsoft_teams_list_teams","microsoft_teams_read_channel","microsoft_teams_read_chat","microsoft_teams_reply_to_message","microsoft_teams_set_reaction","microsoft_teams_unset_reaction","microsoft_teams_update_channel_message","microsoft_teams_update_chat_message","microsoft_teams_write_channel","microsoft_teams_write_chat","microsoft_word_append","microsoft_word_create","microsoft_word_create_from_template","microsoft_word_export_pdf","microsoft_word_list","microsoft_word_read","microsoft_word_replace_text","microsoft_word_update","millionverifier_get_credits","millionverifier_verify_email","mintlify_create_agent_job","mintlify_create_assistant_message","mintlify_detect_ai_prose","mintlify_get_agent_job","mintlify_get_assistant_caller_stats","mintlify_get_assistant_conversations","mintlify_get_feedback","mintlify_get_feedback_by_page","mintlify_get_page_content","mintlify_get_searches","mintlify_get_update_status","mintlify_get_views","mintlify_get_visitors","mintlify_search","mintlify_send_agent_message","mintlify_trigger_automation","mintlify_trigger_preview","mintlify_trigger_update","mistral_parser","mistral_parser_v2","mistral_parser_v3","modal_call_function","modal_chat_completion","modal_list_models","monday_archive_item","monday_change_column_value","monday_create_board","monday_create_column","monday_create_group","monday_create_item","monday_create_subitem","monday_create_update","monday_delete_item","monday_duplicate_item","monday_get_board","monday_get_groups","monday_get_item","monday_get_items","monday_list_boards","monday_move_item_to_group","monday_search_items","monday_update_item","mongodb_delete","mongodb_execute","mongodb_insert","mongodb_introspect","mongodb_query","mongodb_update","mssql_delete","mssql_execute","mssql_insert","mssql_introspect","mssql_query","mssql_update","mysql_delete","mysql_execute","mysql_insert","mysql_introspect","mysql_query","mysql_update","neo4j_create","neo4j_delete","neo4j_execute","neo4j_introspect","neo4j_merge","neo4j_query","neo4j_update","netsuite_attach_record","netsuite_batch_create_records","netsuite_batch_delete_records","netsuite_batch_get_records","netsuite_batch_update_records","netsuite_batch_upsert_records","netsuite_create_record","netsuite_delete_record","netsuite_detach_record","netsuite_execute_action","netsuite_execute_dataset","netsuite_execute_suiteql","netsuite_get_async_result","netsuite_get_async_status","netsuite_get_governance_limits","netsuite_get_record","netsuite_get_record_form","netsuite_get_record_metadata","netsuite_get_select_options","netsuite_get_server_time","netsuite_get_subresource","netsuite_list_datasets","netsuite_list_record_types","netsuite_list_records","netsuite_transform_record","netsuite_update_record","netsuite_upsert_record","neverbounce_get_credits","neverbounce_verify_email","new_relic_create_deployment_event","new_relic_get_entity","new_relic_nrql_query","new_relic_search_entities","notion_add_database_row","notion_add_database_row_v2","notion_append_blocks","notion_append_blocks_v2","notion_create_comment","notion_create_comment_v2","notion_create_database","notion_create_database_v2","notion_create_page","notion_create_page_v2","notion_delete_block","notion_delete_block_v2","notion_list_comments","notion_list_comments_v2","notion_list_users","notion_list_users_v2","notion_query_database","notion_query_database_v2","notion_read","notion_read_database","notion_read_database_v2","notion_read_v2","notion_retrieve_block","notion_retrieve_block_children","notion_retrieve_block_children_v2","notion_retrieve_block_v2","notion_retrieve_user","notion_retrieve_user_v2","notion_search","notion_search_v2","notion_update_block","notion_update_block_v2","notion_update_page","notion_update_page_v2","notion_write","notion_write_v2","obsidian_append_active","obsidian_append_note","obsidian_append_periodic_note","obsidian_create_note","obsidian_delete_note","obsidian_execute_command","obsidian_get_active","obsidian_get_note","obsidian_get_periodic_note","obsidian_list_commands","obsidian_list_files","obsidian_open_file","obsidian_patch_active","obsidian_patch_note","obsidian_search","okta_activate_group_rule","okta_activate_user","okta_add_user_to_group","okta_assign_group_to_app","okta_assign_user_role","okta_assign_user_to_app","okta_clear_user_sessions","okta_create_group","okta_create_group_rule","okta_create_user","okta_deactivate_group_rule","okta_deactivate_user","okta_delete_group","okta_delete_group_rule","okta_delete_user","okta_enroll_factor","okta_get_app","okta_get_factor","okta_get_group","okta_get_group_rule","okta_get_logs","okta_get_session","okta_get_user","okta_list_app_groups","okta_list_app_users","okta_list_apps","okta_list_factors","okta_list_group_members","okta_list_group_rules","okta_list_groups","okta_list_user_roles","okta_list_users","okta_remove_group_from_app","okta_remove_user_from_app","okta_remove_user_from_group","okta_remove_user_role","okta_reset_all_factors","okta_reset_factor","okta_reset_password","okta_revoke_session","okta_suspend_user","okta_unsuspend_user","okta_update_group","okta_update_user","onedrive_copy","onedrive_create_folder","onedrive_create_share_link","onedrive_delete","onedrive_download","onedrive_get_drive_info","onedrive_get_item","onedrive_list","onedrive_move","onedrive_search","onedrive_upload","onepassword_create_item","onepassword_delete_item","onepassword_get_item","onepassword_get_item_file","onepassword_get_vault","onepassword_list_items","onepassword_list_vaults","onepassword_replace_item","onepassword_resolve_secret","onepassword_update_item","openai_embeddings","openai_image","outlook_calendar_create_event","outlook_calendar_delete_event","outlook_calendar_get_event","outlook_calendar_list_events","outlook_calendar_respond","outlook_calendar_update_event","outlook_copy","outlook_create_folder","outlook_delete","outlook_draft","outlook_forward","outlook_get_attachment","outlook_list_attachments","outlook_list_folders","outlook_mark_read","outlook_mark_unread","outlook_move","outlook_read","outlook_reply","outlook_reply_all","outlook_search","outlook_send","outlook_update_message","pagerduty_add_note","pagerduty_create_incident","pagerduty_get_incident","pagerduty_get_service","pagerduty_list_escalation_policies","pagerduty_list_incident_alerts","pagerduty_list_incidents","pagerduty_list_oncalls","pagerduty_list_schedules","pagerduty_list_services","pagerduty_list_users","pagerduty_merge_incidents","pagerduty_send_event","pagerduty_snooze_incident","pagerduty_update_incident","parallel_deep_research","parallel_extract","parallel_search","pdl_autocomplete","pdl_bulk_company_enrich","pdl_bulk_person_enrich","pdl_clean_company","pdl_clean_location","pdl_clean_school","pdl_company_enrich","pdl_company_search","pdl_person_enrich","pdl_person_identify","pdl_person_search","perplexity_chat","perplexity_search","persona_approve_inquiry","persona_create_account","persona_create_inquiry","persona_create_report","persona_decline_inquiry","persona_expire_inquiry","persona_generate_inquiry_link","persona_get_account","persona_get_case","persona_get_document","persona_get_inquiry","persona_get_report","persona_get_verification","persona_import_accounts","persona_list_accounts","persona_list_cases","persona_list_inquiries","persona_list_inquiry_templates","persona_list_reports","persona_mark_inquiry_for_review","persona_print_inquiry_pdf","persona_redact_account","persona_redact_inquiry","persona_resume_inquiry","persona_update_account","persona_update_inquiry","pinecone_delete_vectors","pinecone_describe_index","pinecone_describe_index_stats","pinecone_fetch","pinecone_generate_embeddings","pinecone_list_indexes","pinecone_list_vector_ids","pinecone_search_text","pinecone_search_vector","pinecone_update_vector","pinecone_upsert_text","pipedrive_create_activity","pipedrive_create_deal","pipedrive_create_lead","pipedrive_create_project","pipedrive_delete_lead","pipedrive_get_activities","pipedrive_get_all_deals","pipedrive_get_deal","pipedrive_get_files","pipedrive_get_leads","pipedrive_get_mail_messages","pipedrive_get_mail_thread","pipedrive_get_pipeline_deals","pipedrive_get_pipelines","pipedrive_get_projects","pipedrive_update_activity","pipedrive_update_deal","pipedrive_update_lead","pitchbook_company_active_investors","pitchbook_company_bio","pitchbook_company_deal_service_providers","pitchbook_company_deals","pitchbook_company_financials","pitchbook_company_general_service_providers","pitchbook_company_industries","pitchbook_company_investors","pitchbook_company_most_recent_debt_financing","pitchbook_company_most_recent_financials","pitchbook_company_most_recent_financing","pitchbook_company_search","pitchbook_company_similar_companies","pitchbook_company_social_analytics","pitchbook_company_updates","pitchbook_company_vc_exit_predictions","pitchbook_contracts_history","pitchbook_cost_of_calls","pitchbook_credit_history","pitchbook_credit_news","pitchbook_credit_news_bulk","pitchbook_credit_news_most_recent","pitchbook_credit_news_search","pitchbook_deal_bio","pitchbook_deal_cap_table_history","pitchbook_deal_debt_lenders","pitchbook_deal_detailed","pitchbook_deal_investors","pitchbook_deal_multiples","pitchbook_deal_search","pitchbook_deal_service_providers","pitchbook_deal_stock_info","pitchbook_deal_tranche_info","pitchbook_deal_updates","pitchbook_deal_valuation","pitchbook_entity_affiliates","pitchbook_entity_locations","pitchbook_entity_news","pitchbook_entity_people","pitchbook_entity_updates","pitchbook_fund_active_investments","pitchbook_fund_benchmark","pitchbook_fund_bio","pitchbook_fund_cash_flows","pitchbook_fund_commitments","pitchbook_fund_investment_preferences","pitchbook_fund_investments","pitchbook_fund_performance","pitchbook_fund_search","pitchbook_fund_team","pitchbook_fund_updates","pitchbook_investor_active_investments","pitchbook_investor_bio","pitchbook_investor_board_seats","pitchbook_investor_deal_service_providers","pitchbook_investor_funds","pitchbook_investor_general_service_providers","pitchbook_investor_investments","pitchbook_investor_last_closed_fund","pitchbook_investor_preferences","pitchbook_investor_search","pitchbook_investor_updates","pitchbook_limited_partner_actual_allocations","pitchbook_limited_partner_bio","pitchbook_limited_partner_commitment_aggregates","pitchbook_limited_partner_commitment_preferences","pitchbook_limited_partner_commitments_detailed","pitchbook_limited_partner_search","pitchbook_limited_partner_service_providers","pitchbook_limited_partner_target_allocations","pitchbook_limited_partner_updates","pitchbook_lookup_table_structure","pitchbook_lookup_tables","pitchbook_patent_detailed","pitchbook_patent_search","pitchbook_people_search","pitchbook_person_bio","pitchbook_person_contact","pitchbook_person_education_work","pitchbook_sandbox_entities","pitchbook_search","pitchbook_service_provider_bio","pitchbook_service_provider_search","pitchbook_service_provider_updates","pitchbook_serviced_companies","pitchbook_serviced_deals","pitchbook_serviced_funds","pitchbook_serviced_investors","pitchbook_serviced_limited_partners","pitchbook_shared_search","pitchbook_usage_report","polymarket_get_activity","polymarket_get_event","polymarket_get_events","polymarket_get_holders","polymarket_get_last_trade_price","polymarket_get_leaderboard","polymarket_get_market","polymarket_get_markets","polymarket_get_midpoint","polymarket_get_orderbook","polymarket_get_positions","polymarket_get_price","polymarket_get_price_history","polymarket_get_series","polymarket_get_series_by_id","polymarket_get_spread","polymarket_get_tags","polymarket_get_tick_size","polymarket_get_trades","polymarket_search","postgresql_delete","postgresql_execute","postgresql_insert","postgresql_introspect","postgresql_query","postgresql_update","posthog_batch_events","posthog_capture_event","posthog_create_annotation","posthog_create_cohort","posthog_create_dashboard","posthog_create_experiment","posthog_create_feature_flag","posthog_create_insight","posthog_create_survey","posthog_delete_feature_flag","posthog_delete_person","posthog_delete_survey","posthog_evaluate_flags","posthog_get_cohort","posthog_get_dashboard","posthog_get_event_definition","posthog_get_experiment","posthog_get_feature_flag","posthog_get_insight","posthog_get_organization","posthog_get_person","posthog_get_project","posthog_get_property_definition","posthog_get_session_recording","posthog_get_survey","posthog_list_actions","posthog_list_annotations","posthog_list_cohorts","posthog_list_dashboards","posthog_list_event_definitions","posthog_list_experiments","posthog_list_feature_flags","posthog_list_insights","posthog_list_organizations","posthog_list_persons","posthog_list_projects","posthog_list_property_definitions","posthog_list_recording_playlists","posthog_list_session_recordings","posthog_list_surveys","posthog_query","posthog_update_cohort","posthog_update_event_definition","posthog_update_experiment","posthog_update_feature_flag","posthog_update_insight","posthog_update_property_definition","posthog_update_survey","profound_bot_logs","profound_bots_report","profound_category_assets","profound_category_personas","profound_category_prompts","profound_category_tags","profound_category_topics","profound_citation_prompts","profound_citations_report","profound_list_assets","profound_list_categories","profound_list_domains","profound_list_models","profound_list_optimizations","profound_list_personas","profound_list_regions","profound_optimization_analysis","profound_prompt_answers","profound_prompt_volume","profound_query_fanouts","profound_raw_logs","profound_referrals_report","profound_sentiment_report","profound_visibility_report","prospeo_account_information","prospeo_bulk_enrich_company","prospeo_bulk_enrich_person","prospeo_enrich_company","prospeo_enrich_person","prospeo_search_company","prospeo_search_person","prospeo_search_suggestions","pulse_parser","pulse_parser_v2","qdrant_fetch_points","qdrant_search_vector","qdrant_upsert_points","quartr_get_audio","quartr_get_company","quartr_get_event","quartr_get_event_summary","quartr_get_report","quartr_get_slide_deck","quartr_get_transcript","quartr_list_audio","quartr_list_companies","quartr_list_document_types","quartr_list_documents","quartr_list_event_types","quartr_list_events","quartr_list_live_events","quartr_list_reports","quartr_list_slide_decks","quartr_list_transcripts","quickbooks_add_attachment","quickbooks_create_bill","quickbooks_create_bill_payment","quickbooks_create_credit_memo","quickbooks_create_customer","quickbooks_create_customer_payment","quickbooks_create_deposit","quickbooks_create_employee","quickbooks_create_estimate","quickbooks_create_invoice","quickbooks_create_item","quickbooks_create_journal_entry","quickbooks_create_purchase","quickbooks_create_purchase_order","quickbooks_create_refund_receipt","quickbooks_create_sales_receipt","quickbooks_create_vendor","quickbooks_create_vendor_credit","quickbooks_download_attachment","quickbooks_download_transaction_pdf","quickbooks_email_transaction","quickbooks_get_company_info","quickbooks_read_accounting_transactions","quickbooks_read_attachments","quickbooks_read_master_data","quickbooks_read_purchasing_transactions","quickbooks_read_sales_transactions","quickbooks_run_financial_report","quickbooks_update_bill","quickbooks_update_bill_payment","quickbooks_update_credit_memo","quickbooks_update_customer","quickbooks_update_customer_payment","quickbooks_update_deposit","quickbooks_update_employee","quickbooks_update_estimate","quickbooks_update_invoice","quickbooks_update_item","quickbooks_update_journal_entry","quickbooks_update_purchase","quickbooks_update_purchase_order","quickbooks_update_refund_receipt","quickbooks_update_sales_receipt","quickbooks_update_vendor","quickbooks_update_vendor_credit","quickbooks_void_bill_payment","quickbooks_void_customer_payment","quickbooks_void_invoice","quickbooks_void_sales_receipt","quiver_image_to_svg","quiver_image_to_svg_v2","quiver_list_models","quiver_text_to_svg","quiver_text_to_svg_v2","rabbitmq_create_binding","rabbitmq_create_exchange","rabbitmq_create_policy","rabbitmq_create_queue","rabbitmq_delete_binding","rabbitmq_delete_exchange","rabbitmq_delete_policy","rabbitmq_delete_queue","rabbitmq_get_exchange","rabbitmq_get_messages","rabbitmq_get_overview","rabbitmq_get_queue","rabbitmq_health_check","rabbitmq_list_bindings","rabbitmq_list_channels","rabbitmq_list_connections","rabbitmq_list_consumers","rabbitmq_list_exchange_bindings","rabbitmq_list_exchanges","rabbitmq_list_nodes","rabbitmq_list_policies","rabbitmq_list_queues","rabbitmq_list_vhosts","rabbitmq_publish_message","rabbitmq_purge_queue","railway_create_environment","railway_create_project","railway_create_service","railway_delete_environment","railway_delete_project","railway_delete_service","railway_delete_variable","railway_deploy_service","railway_get_deployment","railway_get_deployment_logs","railway_get_project","railway_list_deployments","railway_list_project_members","railway_list_projects","railway_list_variables","railway_restart_deployment","railway_rollback_deployment","railway_transfer_project","railway_update_project","railway_upsert_variable","rb2b_credit_check","rb2b_email_to_activity","rb2b_hem_to_best_linkedin","rb2b_hem_to_business_profile","rb2b_hem_to_linkedin","rb2b_hem_to_maid","rb2b_ip_to_company","rb2b_ip_to_hem","rb2b_ip_to_maid","rb2b_linkedin_slug_search","rb2b_linkedin_to_best_personal_email","rb2b_linkedin_to_business_profile","rb2b_linkedin_to_hashed_emails","rb2b_linkedin_to_mobile_phone","rb2b_linkedin_to_personal_email","rds_delete","rds_execute","rds_insert","rds_introspect","rds_query","rds_update","reddit_delete","reddit_edit","reddit_get_comments","reddit_get_controversial","reddit_get_info","reddit_get_me","reddit_get_messages","reddit_get_posts","reddit_get_saved","reddit_get_subreddit_info","reddit_get_subreddit_rules","reddit_get_user","reddit_get_user_comments","reddit_get_user_posts","reddit_hide","reddit_hot_posts","reddit_list_my_subreddits","reddit_lock","reddit_mark_all_read","reddit_mark_read","reddit_marknsfw","reddit_mod_approve","reddit_mod_distinguish","reddit_mod_remove","reddit_mod_sticky","reddit_reply","reddit_report","reddit_save","reddit_search","reddit_search_subreddits","reddit_send_message","reddit_submit_post","reddit_subscribe","reddit_unhide","reddit_unlock","reddit_unmarknsfw","reddit_unsave","reddit_vote","redis_command","redis_delete","redis_exists","redis_expire","redis_get","redis_hdel","redis_hget","redis_hgetall","redis_hset","redis_incr","redis_incrby","redis_keys","redis_llen","redis_lpop","redis_lpush","redis_lrange","redis_persist","redis_rpop","redis_rpush","redis_set","redis_setnx","redis_ttl","reducto_parser","reducto_parser_v2","resend_cancel_email","resend_create_audience","resend_create_broadcast","resend_create_contact","resend_delete_audience","resend_delete_contact","resend_get_audience","resend_get_broadcast","resend_get_contact","resend_get_email","resend_list_audiences","resend_list_contacts","resend_list_domains","resend_send","resend_send_broadcast","resend_update_contact","revenuecat_create_purchase","revenuecat_defer_google_subscription","revenuecat_delete_customer","revenuecat_get_customer","revenuecat_grant_entitlement","revenuecat_list_offerings","revenuecat_refund_google_subscription","revenuecat_revoke_entitlement","revenuecat_revoke_google_subscription","revenuecat_update_subscriber_attributes","rippling_bulk_create_custom_object_records","rippling_bulk_delete_custom_object_records","rippling_bulk_update_custom_object_records","rippling_create_business_partner","rippling_create_business_partner_group","rippling_create_custom_app","rippling_create_custom_object","rippling_create_custom_object_field","rippling_create_custom_object_record","rippling_create_custom_page","rippling_create_custom_setting","rippling_create_department","rippling_create_draft_hires","rippling_create_object_category","rippling_create_title","rippling_create_work_location","rippling_delete_business_partner","rippling_delete_business_partner_group","rippling_delete_custom_app","rippling_delete_custom_object","rippling_delete_custom_object_field","rippling_delete_custom_object_record","rippling_delete_custom_page","rippling_delete_custom_setting","rippling_delete_object_category","rippling_delete_title","rippling_delete_work_location","rippling_get_business_partner","rippling_get_business_partner_group","rippling_get_current_user","rippling_get_custom_app","rippling_get_custom_object","rippling_get_custom_object_field","rippling_get_custom_object_record","rippling_get_custom_object_record_by_external_id","rippling_get_custom_page","rippling_get_custom_setting","rippling_get_department","rippling_get_employment_type","rippling_get_job_function","rippling_get_object_category","rippling_get_report_run","rippling_get_supergroup","rippling_get_team","rippling_get_title","rippling_get_user","rippling_get_work_location","rippling_get_worker","rippling_list_business_partner_groups","rippling_list_business_partners","rippling_list_companies","rippling_list_custom_apps","rippling_list_custom_fields","rippling_list_custom_object_fields","rippling_list_custom_object_records","rippling_list_custom_objects","rippling_list_custom_pages","rippling_list_custom_settings","rippling_list_departments","rippling_list_employment_types","rippling_list_entitlements","rippling_list_job_functions","rippling_list_object_categories","rippling_list_supergroup_exclusion_members","rippling_list_supergroup_inclusion_members","rippling_list_supergroup_members","rippling_list_supergroups","rippling_list_teams","rippling_list_titles","rippling_list_users","rippling_list_work_locations","rippling_list_workers","rippling_query_custom_object_records","rippling_trigger_report_run","rippling_update_custom_app","rippling_update_custom_object","rippling_update_custom_object_field","rippling_update_custom_object_record","rippling_update_custom_page","rippling_update_custom_setting","rippling_update_department","rippling_update_object_category","rippling_update_supergroup_exclusion_members","rippling_update_supergroup_inclusion_members","rippling_update_title","rippling_update_work_location","rocketlane_add_field_option","rocketlane_add_project_members","rocketlane_add_task_assignees","rocketlane_add_task_dependencies","rocketlane_add_task_followers","rocketlane_archive_project","rocketlane_assign_placeholders","rocketlane_create_field","rocketlane_create_phase","rocketlane_create_project","rocketlane_create_space","rocketlane_create_space_document","rocketlane_create_task","rocketlane_create_time_entry","rocketlane_create_time_off","rocketlane_delete_field","rocketlane_delete_phase","rocketlane_delete_project","rocketlane_delete_space","rocketlane_delete_space_document","rocketlane_delete_task","rocketlane_delete_time_entry","rocketlane_delete_time_off","rocketlane_get_field","rocketlane_get_invoice","rocketlane_get_invoice_line_items","rocketlane_get_invoice_payments","rocketlane_get_phase","rocketlane_get_project","rocketlane_get_space","rocketlane_get_space_document","rocketlane_get_task","rocketlane_get_time_entry","rocketlane_get_time_off","rocketlane_get_user","rocketlane_import_template","rocketlane_list_fields","rocketlane_list_invoices","rocketlane_list_phases","rocketlane_list_placeholders","rocketlane_list_projects","rocketlane_list_resource_allocations","rocketlane_list_space_documents","rocketlane_list_spaces","rocketlane_list_tasks","rocketlane_list_time_entries","rocketlane_list_time_entry_categories","rocketlane_list_time_offs","rocketlane_list_users","rocketlane_move_task_to_phase","rocketlane_remove_project_members","rocketlane_remove_task_assignees","rocketlane_remove_task_dependencies","rocketlane_remove_task_followers","rocketlane_search_time_entries","rocketlane_unassign_placeholders","rocketlane_update_field","rocketlane_update_field_option","rocketlane_update_phase","rocketlane_update_project","rocketlane_update_space","rocketlane_update_space_document","rocketlane_update_task","rocketlane_update_time_entry","rootly_acknowledge_alert","rootly_add_incident_event","rootly_add_subscribers","rootly_assign_incident_role","rootly_create_action_item","rootly_create_alert","rootly_create_incident","rootly_create_status_page_event","rootly_delete_action_item","rootly_delete_incident","rootly_escalate_alert","rootly_get_alert","rootly_get_incident","rootly_list_action_items","rootly_list_alerts","rootly_list_causes","rootly_list_environments","rootly_list_escalation_policies","rootly_list_functionalities","rootly_list_incident_events","rootly_list_incident_roles","rootly_list_incident_types","rootly_list_incidents","rootly_list_on_calls","rootly_list_playbooks","rootly_list_retrospectives","rootly_list_schedules","rootly_list_services","rootly_list_severities","rootly_list_teams","rootly_list_users","rootly_mitigate_incident","rootly_remove_subscribers","rootly_resolve_alert","rootly_resolve_incident","rootly_run_workflow","rootly_snooze_alert","rootly_unassign_incident_role","rootly_update_action_item","rootly_update_alert","rootly_update_incident","s3_copy_object","s3_create_bucket","s3_delete_bucket","s3_delete_object","s3_delete_objects","s3_get_object","s3_head_object","s3_list_buckets","s3_list_objects","s3_presigned_url","s3_put_object","sailpoint_approve_access_request","sailpoint_cancel_access_request","sailpoint_decide_certification_review_items","sailpoint_get_access_profile","sailpoint_get_access_profile_entitlements","sailpoint_get_access_request_config","sailpoint_get_access_request_status","sailpoint_get_account","sailpoint_get_account_activity","sailpoint_get_account_entitlements","sailpoint_get_account_selections","sailpoint_get_campaign","sailpoint_get_certification","sailpoint_get_entitlement","sailpoint_get_entitlement_request_config","sailpoint_get_identity","sailpoint_get_role","sailpoint_get_role_entitlements","sailpoint_get_source","sailpoint_get_task_status","sailpoint_list_access_profiles","sailpoint_list_account_activities","sailpoint_list_accounts","sailpoint_list_campaigns","sailpoint_list_certification_review_items","sailpoint_list_certifications","sailpoint_list_entitlements","sailpoint_list_identities","sailpoint_list_identity_entitlements","sailpoint_list_pending_access_request_approvals","sailpoint_list_roles","sailpoint_list_sources","sailpoint_load_accounts","sailpoint_load_entitlements","sailpoint_reject_access_request","sailpoint_request_access","sailpoint_search","sailpoint_search_aggregate","sailpoint_search_count","sailpoint_sign_off_certification","salesforce_create_account","salesforce_create_case","salesforce_create_contact","salesforce_create_custom_field","salesforce_create_custom_object","salesforce_create_lead","salesforce_create_opportunity","salesforce_create_task","salesforce_delete_account","salesforce_delete_case","salesforce_delete_contact","salesforce_delete_custom_field","salesforce_delete_lead","salesforce_delete_opportunity","salesforce_delete_task","salesforce_describe_object","salesforce_get_accounts","salesforce_get_cases","salesforce_get_contacts","salesforce_get_dashboard","salesforce_get_leads","salesforce_get_opportunities","salesforce_get_report","salesforce_get_tasks","salesforce_list_dashboards","salesforce_list_objects","salesforce_list_report_types","salesforce_list_reports","salesforce_query","salesforce_query_more","salesforce_refresh_dashboard","salesforce_run_report","salesforce_tooling_query","salesforce_update_account","salesforce_update_case","salesforce_update_contact","salesforce_update_custom_field","salesforce_update_lead","salesforce_update_opportunity","salesforce_update_task","sap_concur_approve_expense_report","sap_concur_associate_attendees","sap_concur_create_cash_advance","sap_concur_create_expected_expense","sap_concur_create_expense_report","sap_concur_create_list_item","sap_concur_create_purchase_request","sap_concur_create_quick_expense","sap_concur_create_quick_expense_with_image","sap_concur_create_report_comment","sap_concur_create_travel_request","sap_concur_create_user","sap_concur_delete_expected_expense","sap_concur_delete_expense","sap_concur_delete_expense_report","sap_concur_delete_list_item","sap_concur_delete_travel_request","sap_concur_delete_user","sap_concur_get_allocation","sap_concur_get_budget","sap_concur_get_cash_advance","sap_concur_get_expected_expense","sap_concur_get_expense","sap_concur_get_expense_report","sap_concur_get_itemizations","sap_concur_get_itinerary","sap_concur_get_list","sap_concur_get_list_item","sap_concur_get_purchase_request","sap_concur_get_receipt","sap_concur_get_receipt_status","sap_concur_get_request_cash_advance","sap_concur_get_travel_profile","sap_concur_get_travel_request","sap_concur_get_user","sap_concur_issue_cash_advance","sap_concur_list_allocations","sap_concur_list_attendee_associations","sap_concur_list_budget_categories","sap_concur_list_budgets","sap_concur_list_exceptions","sap_concur_list_expected_expenses","sap_concur_list_expense_reports","sap_concur_list_expenses","sap_concur_list_itineraries","sap_concur_list_list_items","sap_concur_list_lists","sap_concur_list_receipts","sap_concur_list_report_comments","sap_concur_list_reports_to_approve","sap_concur_list_travel_profiles_summary","sap_concur_list_travel_request_comments","sap_concur_list_travel_requests","sap_concur_list_users","sap_concur_move_travel_request","sap_concur_recall_expense_report","sap_concur_remove_all_attendees","sap_concur_search_locations","sap_concur_search_users","sap_concur_send_back_expense_report","sap_concur_submit_expense_report","sap_concur_update_allocation","sap_concur_update_expected_expense","sap_concur_update_expense","sap_concur_update_expense_report","sap_concur_update_list_item","sap_concur_update_travel_request","sap_concur_update_user","sap_concur_upload_exchange_rates","sap_concur_upload_receipt_image","sap_s4hana_create_business_partner","sap_s4hana_create_purchase_order","sap_s4hana_create_purchase_requisition","sap_s4hana_create_sales_order","sap_s4hana_delete_sales_order","sap_s4hana_get_billing_document","sap_s4hana_get_business_partner","sap_s4hana_get_customer","sap_s4hana_get_inbound_delivery","sap_s4hana_get_material_document","sap_s4hana_get_outbound_delivery","sap_s4hana_get_product","sap_s4hana_get_purchase_order","sap_s4hana_get_purchase_requisition","sap_s4hana_get_sales_order","sap_s4hana_get_supplier","sap_s4hana_get_supplier_invoice","sap_s4hana_list_billing_documents","sap_s4hana_list_business_partners","sap_s4hana_list_customers","sap_s4hana_list_inbound_deliveries","sap_s4hana_list_material_documents","sap_s4hana_list_material_stock","sap_s4hana_list_outbound_deliveries","sap_s4hana_list_products","sap_s4hana_list_purchase_orders","sap_s4hana_list_purchase_requisitions","sap_s4hana_list_sales_orders","sap_s4hana_list_supplier_invoices","sap_s4hana_list_suppliers","sap_s4hana_odata_query","sap_s4hana_update_business_partner","sap_s4hana_update_customer","sap_s4hana_update_product","sap_s4hana_update_purchase_order","sap_s4hana_update_purchase_requisition","sap_s4hana_update_sales_order","sap_s4hana_update_supplier","search_tool","secrets_manager_create_secret","secrets_manager_delete_secret","secrets_manager_describe_secret","secrets_manager_get_secret","secrets_manager_list_secrets","secrets_manager_restore_secret","secrets_manager_rotate_secret","secrets_manager_tag_resource","secrets_manager_untag_resource","secrets_manager_update_secret","semrush_backlinks","semrush_backlinks_anchors","semrush_backlinks_competitors","semrush_backlinks_geo_distribution","semrush_backlinks_indexed_pages","semrush_backlinks_overview","semrush_backlinks_tld_distribution","semrush_batch_keyword_overview","semrush_broad_match_keywords","semrush_domain_ad_copies","semrush_domain_ad_history","semrush_domain_organic_competitors","semrush_domain_organic_keywords","semrush_domain_overview","semrush_domain_overview_all","semrush_domain_overview_history","semrush_domain_paid_competitors","semrush_domain_paid_keywords","semrush_domain_pla_copies","semrush_domain_pla_keywords","semrush_domain_vs_domain","semrush_keyword_ad_history","semrush_keyword_difficulty","semrush_keyword_overview","semrush_keyword_overview_all","semrush_keyword_questions","semrush_organic_results","semrush_paid_results","semrush_referring_domains","semrush_referring_ips","semrush_related_keywords","semrush_subdomain_ad_copies","semrush_subdomain_organic_keywords","semrush_subdomain_overview","semrush_subdomain_overview_all","semrush_subdomain_overview_history","semrush_subdomain_paid_keywords","semrush_top_domains","semrush_url_organic_keywords","semrush_url_overview","semrush_url_overview_all","semrush_url_overview_history","semrush_url_paid_keywords","semrush_winners_and_losers","sendblue_evaluate_service","sendblue_get_message","sendblue_send_group_message","sendblue_send_message","sendblue_send_typing_indicator","sendgrid_add_contact","sendgrid_add_contacts_to_list","sendgrid_create_list","sendgrid_create_template","sendgrid_create_template_version","sendgrid_delete_contacts","sendgrid_delete_list","sendgrid_delete_template","sendgrid_get_contact","sendgrid_get_list","sendgrid_get_template","sendgrid_list_all_lists","sendgrid_list_templates","sendgrid_remove_contacts_from_list","sendgrid_search_contacts","sendgrid_send_mail","sentry_events_get","sentry_events_list","sentry_issues_get","sentry_issues_list","sentry_issues_update","sentry_projects_create","sentry_projects_get","sentry_projects_list","sentry_projects_update","sentry_releases_create","sentry_releases_deploy","sentry_releases_list","sentry_teams_list","serper_search","servicenow_add_incident_comment","servicenow_aggregate","servicenow_close_incident","servicenow_create_change_request","servicenow_create_incident","servicenow_create_record","servicenow_delete_record","servicenow_download_attachment","servicenow_download_attachment_v2","servicenow_find_user","servicenow_get_change_next_states","servicenow_get_change_request","servicenow_get_ci","servicenow_get_incident","servicenow_get_knowledge_article","servicenow_get_requested_item","servicenow_list_approvals","servicenow_list_attachments","servicenow_list_catalog_items","servicenow_list_change_requests","servicenow_list_change_tasks","servicenow_list_ci_relationships","servicenow_list_group_members","servicenow_list_incidents","servicenow_list_requested_items","servicenow_order_catalog_item","servicenow_read_record","servicenow_resolve_incident","servicenow_search_cis","servicenow_search_knowledge","servicenow_update_approval","servicenow_update_change_request","servicenow_update_change_state","servicenow_update_incident","servicenow_update_record","servicenow_upload_attachment","ses_create_configuration_set","ses_create_email_identity","ses_create_template","ses_delete_email_identity","ses_delete_suppressed_destination","ses_delete_template","ses_get_account","ses_get_email_identity","ses_get_suppressed_destination","ses_get_template","ses_list_identities","ses_list_suppressed_destinations","ses_list_templates","ses_put_suppressed_destination","ses_send_bulk_email","ses_send_custom_verification_email","ses_send_email","ses_send_templated_email","ses_update_template","sftp_delete","sftp_download","sftp_download_v2","sftp_list","sftp_mkdir","sftp_upload","sharepoint_add_list_items","sharepoint_create_list","sharepoint_create_page","sharepoint_delete_file","sharepoint_delete_list_item","sharepoint_delete_page","sharepoint_download_file","sharepoint_get_drive_item","sharepoint_get_list","sharepoint_get_list_item","sharepoint_list_sites","sharepoint_publish_page","sharepoint_read_page","sharepoint_update_list","sharepoint_update_page","sharepoint_upload_file","shopify_adjust_inventory","shopify_cancel_order","shopify_create_customer","shopify_create_fulfillment","shopify_create_product","shopify_delete_customer","shopify_delete_product","shopify_get_collection","shopify_get_customer","shopify_get_inventory_level","shopify_get_order","shopify_get_product","shopify_list_collections","shopify_list_customers","shopify_list_inventory_items","shopify_list_locations","shopify_list_orders","shopify_list_products","shopify_update_customer","shopify_update_order","shopify_update_product","similarweb_bounce_rate","similarweb_page_views","similarweb_pages_per_visit","similarweb_traffic_visits","similarweb_visit_duration","similarweb_website_overview","sixtyfour_enrich_company","sixtyfour_enrich_lead","sixtyfour_find_email","sixtyfour_find_phone","slack_add_reaction","slack_archive_conversation","slack_canvas","slack_create_channel_canvas","slack_create_conversation","slack_delete_canvas","slack_delete_message","slack_delete_scheduled_message","slack_download","slack_edit_canvas","slack_ephemeral_message","slack_get_canvas","slack_get_channel_history","slack_get_channel_info","slack_get_message","slack_get_permalink","slack_get_thread","slack_get_thread_replies","slack_get_user","slack_get_user_presence","slack_invite_to_conversation","slack_list_canvases","slack_list_channels","slack_list_members","slack_list_scheduled_messages","slack_list_users","slack_lookup_canvas_sections","slack_message","slack_message_reader","slack_open_view","slack_publish_view","slack_push_view","slack_remove_reaction","slack_rename_agent_session_v2","slack_rename_conversation","slack_schedule_message","slack_set_agent_session_status_v2","slack_set_conversation_purpose","slack_set_conversation_topic","slack_set_status","slack_set_suggested_prompts","slack_set_suggested_prompts_v2","slack_set_title","slack_update_message","slack_update_view","smartlead_add_email_accounts_to_campaign","smartlead_add_leads_to_campaign","smartlead_create_campaign","smartlead_create_lead_list","smartlead_delete_campaign","smartlead_delete_campaign_webhook","smartlead_delete_lead_from_campaign","smartlead_delete_lead_list","smartlead_duplicate_campaign","smartlead_export_campaign_leads","smartlead_get_campaign","smartlead_get_campaign_analytics","smartlead_get_campaign_analytics_by_date","smartlead_get_campaign_lead_statistics","smartlead_get_campaign_mailbox_statistics","smartlead_get_campaign_sequences","smartlead_get_campaign_statistics","smartlead_get_campaign_top_level_analytics_by_date","smartlead_get_campaign_webhook_summary","smartlead_get_lead_by_email","smartlead_get_lead_by_id","smartlead_get_lead_list","smartlead_get_lead_message_history","smartlead_list_campaign_email_accounts","smartlead_list_campaign_leads","smartlead_list_campaign_webhooks","smartlead_list_campaigns","smartlead_list_clients","smartlead_list_email_accounts","smartlead_list_inbox_replies","smartlead_list_lead_activities","smartlead_list_lead_categories","smartlead_list_lead_lists","smartlead_mark_lead_complete","smartlead_pause_lead","smartlead_remove_email_accounts_from_campaign","smartlead_resume_lead","smartlead_save_campaign_sequences","smartlead_unsubscribe_lead_from_campaign","smartlead_unsubscribe_lead_globally","smartlead_update_campaign_schedule","smartlead_update_campaign_settings","smartlead_update_campaign_status","smartlead_update_lead","smartlead_update_lead_category","smartlead_update_lead_list","smartlead_upsert_campaign_webhook","sms_send","smtp_send_mail","snowflake_alter_warehouse","snowflake_call_procedure","snowflake_cancel_statement","snowflake_cancel_task_run","snowflake_delete_rows","snowflake_execute_sql","snowflake_get_statement","snowflake_get_task","snowflake_get_task_run","snowflake_get_task_run_output","snowflake_get_warehouse","snowflake_insert_rows","snowflake_introspect_schema","snowflake_list_copy_history","snowflake_list_databases","snowflake_list_query_history","snowflake_list_schemas","snowflake_list_tables","snowflake_list_task_runs","snowflake_list_tasks","snowflake_list_warehouses","snowflake_load_data","snowflake_resume_task","snowflake_resume_warehouse","snowflake_run_task","snowflake_suspend_task","snowflake_suspend_warehouse","snowflake_unload_data","snowflake_update_rows","snowflake_upsert_rows","splunk_cancel_search_job","splunk_create_search_job","splunk_dispatch_saved_search","splunk_get_fired_alerts","splunk_get_saved_search","splunk_get_search_job","splunk_get_search_results","splunk_list_apps","splunk_list_fired_alerts","splunk_list_indexes","splunk_list_saved_searches","splunk_run_search","sportmonks_core_get_cities","sportmonks_core_get_city","sportmonks_core_get_continent","sportmonks_core_get_continents","sportmonks_core_get_countries","sportmonks_core_get_country","sportmonks_core_get_entity_filters","sportmonks_core_get_my_usage","sportmonks_core_get_region","sportmonks_core_get_regions","sportmonks_core_get_timezones","sportmonks_core_get_type","sportmonks_core_get_type_by_entity","sportmonks_core_get_types","sportmonks_core_search_cities","sportmonks_core_search_countries","sportmonks_core_search_regions","sportmonks_football_expected_by_player","sportmonks_football_expected_by_team","sportmonks_football_get_all_commentaries","sportmonks_football_get_all_fixtures","sportmonks_football_get_all_players","sportmonks_football_get_all_rivals","sportmonks_football_get_all_teams","sportmonks_football_get_all_transfer_rumours","sportmonks_football_get_all_transfers","sportmonks_football_get_brackets_by_season","sportmonks_football_get_coach","sportmonks_football_get_coaches","sportmonks_football_get_coaches_by_country","sportmonks_football_get_commentaries_by_fixture","sportmonks_football_get_current_leagues_by_team","sportmonks_football_get_expected_lineups_by_player","sportmonks_football_get_expected_lineups_by_team","sportmonks_football_get_extended_team_squad","sportmonks_football_get_fixture","sportmonks_football_get_fixtures_by_date","sportmonks_football_get_fixtures_by_date_range","sportmonks_football_get_fixtures_by_date_range_for_team","sportmonks_football_get_fixtures_by_ids","sportmonks_football_get_grouped_standings_by_round","sportmonks_football_get_head_to_head","sportmonks_football_get_inplay_livescores","sportmonks_football_get_latest_coaches","sportmonks_football_get_latest_fixtures","sportmonks_football_get_latest_livescores","sportmonks_football_get_latest_players","sportmonks_football_get_latest_totw","sportmonks_football_get_latest_transfers","sportmonks_football_get_league","sportmonks_football_get_leagues","sportmonks_football_get_leagues_by_country","sportmonks_football_get_leagues_by_date","sportmonks_football_get_leagues_by_team","sportmonks_football_get_live_leagues","sportmonks_football_get_live_probabilities","sportmonks_football_get_live_probabilities_by_fixture","sportmonks_football_get_live_standings_by_league","sportmonks_football_get_livescores","sportmonks_football_get_match_facts","sportmonks_football_get_match_facts_by_date_range","sportmonks_football_get_match_facts_by_fixture","sportmonks_football_get_match_facts_by_league","sportmonks_football_get_past_fixtures_by_tv_station","sportmonks_football_get_player","sportmonks_football_get_players_by_country","sportmonks_football_get_postmatch_news","sportmonks_football_get_postmatch_news_by_season","sportmonks_football_get_predictability_by_league","sportmonks_football_get_prematch_news","sportmonks_football_get_prematch_news_by_season","sportmonks_football_get_prematch_news_upcoming","sportmonks_football_get_probabilities","sportmonks_football_get_probabilities_by_fixture","sportmonks_football_get_referee","sportmonks_football_get_referees","sportmonks_football_get_referees_by_country","sportmonks_football_get_referees_by_season","sportmonks_football_get_rivals_by_team","sportmonks_football_get_round","sportmonks_football_get_round_statistics","sportmonks_football_get_rounds","sportmonks_football_get_rounds_by_season","sportmonks_football_get_schedules_by_season","sportmonks_football_get_schedules_by_season_and_team","sportmonks_football_get_schedules_by_team","sportmonks_football_get_season","sportmonks_football_get_seasons","sportmonks_football_get_seasons_by_team","sportmonks_football_get_stage","sportmonks_football_get_stage_statistics","sportmonks_football_get_stages","sportmonks_football_get_stages_by_season","sportmonks_football_get_standing_corrections_by_season","sportmonks_football_get_standings","sportmonks_football_get_standings_by_round","sportmonks_football_get_standings_by_season","sportmonks_football_get_state","sportmonks_football_get_states","sportmonks_football_get_team","sportmonks_football_get_team_rankings","sportmonks_football_get_team_rankings_by_date","sportmonks_football_get_team_rankings_by_team","sportmonks_football_get_team_squad","sportmonks_football_get_team_squad_by_season","sportmonks_football_get_teams_by_country","sportmonks_football_get_teams_by_season","sportmonks_football_get_topscorers_by_season","sportmonks_football_get_topscorers_by_stage","sportmonks_football_get_totw","sportmonks_football_get_totw_by_round","sportmonks_football_get_transfer","sportmonks_football_get_transfer_rumour","sportmonks_football_get_transfer_rumours_between_dates","sportmonks_football_get_transfer_rumours_by_player","sportmonks_football_get_transfer_rumours_by_team","sportmonks_football_get_transfers_between_dates","sportmonks_football_get_transfers_by_player","sportmonks_football_get_transfers_by_team","sportmonks_football_get_tv_station","sportmonks_football_get_tv_stations","sportmonks_football_get_tv_stations_by_fixture","sportmonks_football_get_upcoming_fixtures_by_market","sportmonks_football_get_upcoming_fixtures_by_tv_station","sportmonks_football_get_value_bets","sportmonks_football_get_value_bets_by_fixture","sportmonks_football_get_venue","sportmonks_football_get_venues","sportmonks_football_get_venues_by_season","sportmonks_football_search_coaches","sportmonks_football_search_fixtures","sportmonks_football_search_leagues","sportmonks_football_search_players","sportmonks_football_search_referees","sportmonks_football_search_rounds","sportmonks_football_search_seasons","sportmonks_football_search_stages","sportmonks_football_search_teams","sportmonks_football_search_venues","sportmonks_motorsport_get_all_fixtures","sportmonks_motorsport_get_current_leagues_by_team","sportmonks_motorsport_get_driver","sportmonks_motorsport_get_driver_standings","sportmonks_motorsport_get_driver_standings_by_season","sportmonks_motorsport_get_drivers","sportmonks_motorsport_get_drivers_by_country","sportmonks_motorsport_get_drivers_by_season","sportmonks_motorsport_get_fixture","sportmonks_motorsport_get_fixtures_by_date","sportmonks_motorsport_get_fixtures_by_date_range","sportmonks_motorsport_get_fixtures_by_ids","sportmonks_motorsport_get_laps_by_fixture","sportmonks_motorsport_get_laps_by_fixture_and_driver","sportmonks_motorsport_get_laps_by_fixture_and_lap","sportmonks_motorsport_get_latest_laps_by_fixture","sportmonks_motorsport_get_latest_pitstops_by_fixture","sportmonks_motorsport_get_latest_stints_by_fixture","sportmonks_motorsport_get_latest_updated_drivers","sportmonks_motorsport_get_latest_updated_fixtures","sportmonks_motorsport_get_league","sportmonks_motorsport_get_leagues","sportmonks_motorsport_get_leagues_by_country","sportmonks_motorsport_get_leagues_by_date","sportmonks_motorsport_get_leagues_by_live","sportmonks_motorsport_get_leagues_by_team","sportmonks_motorsport_get_livescores","sportmonks_motorsport_get_pitstops_by_fixture","sportmonks_motorsport_get_pitstops_by_fixture_and_driver","sportmonks_motorsport_get_pitstops_by_fixture_and_lap","sportmonks_motorsport_get_race_results_by_season_and_driver","sportmonks_motorsport_get_race_results_by_season_and_team","sportmonks_motorsport_get_schedules_by_season","sportmonks_motorsport_get_season","sportmonks_motorsport_get_seasons","sportmonks_motorsport_get_stage","sportmonks_motorsport_get_stages","sportmonks_motorsport_get_stages_by_season","sportmonks_motorsport_get_state","sportmonks_motorsport_get_states","sportmonks_motorsport_get_stints_by_fixture","sportmonks_motorsport_get_stints_by_fixture_and_driver","sportmonks_motorsport_get_stints_by_fixture_and_stint","sportmonks_motorsport_get_team","sportmonks_motorsport_get_team_standings","sportmonks_motorsport_get_team_standings_by_season","sportmonks_motorsport_get_teams","sportmonks_motorsport_get_teams_by_country","sportmonks_motorsport_get_teams_by_season","sportmonks_motorsport_get_venue","sportmonks_motorsport_get_venues","sportmonks_motorsport_get_venues_by_season","sportmonks_motorsport_search_drivers","sportmonks_motorsport_search_leagues","sportmonks_motorsport_search_stages","sportmonks_motorsport_search_teams","sportmonks_motorsport_search_venues","sportmonks_odds_get_all_historical_odds","sportmonks_odds_get_all_inplay_odds","sportmonks_odds_get_all_pre_match_odds","sportmonks_odds_get_all_premium_odds","sportmonks_odds_get_bookmaker","sportmonks_odds_get_bookmaker_event_ids_by_fixture","sportmonks_odds_get_bookmakers","sportmonks_odds_get_bookmakers_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture","sportmonks_odds_get_inplay_odds_by_fixture_and_bookmaker","sportmonks_odds_get_inplay_odds_by_fixture_and_market","sportmonks_odds_get_last_updated_inplay_odds","sportmonks_odds_get_last_updated_pre_match_odds","sportmonks_odds_get_market","sportmonks_odds_get_markets","sportmonks_odds_get_pre_match_odds_by_fixture","sportmonks_odds_get_pre_match_odds_by_fixture_and_bookmaker","sportmonks_odds_get_pre_match_odds_by_fixture_and_market","sportmonks_odds_get_premium_odds_by_fixture","sportmonks_odds_get_premium_odds_by_fixture_and_bookmaker","sportmonks_odds_get_premium_odds_by_fixture_and_market","sportmonks_odds_get_updated_historical_odds_between","sportmonks_odds_get_updated_premium_odds_between","sportmonks_odds_search_bookmakers","sportmonks_odds_search_markets","spotify_add_playlist_cover","spotify_add_to_queue","spotify_add_tracks_to_playlist","spotify_check_following","spotify_check_playlist_followers","spotify_check_saved_albums","spotify_check_saved_audiobooks","spotify_check_saved_episodes","spotify_check_saved_shows","spotify_check_saved_tracks","spotify_create_playlist","spotify_follow_artists","spotify_follow_playlist","spotify_get_album","spotify_get_album_tracks","spotify_get_albums","spotify_get_artist","spotify_get_artist_albums","spotify_get_artist_top_tracks","spotify_get_artists","spotify_get_audiobook","spotify_get_audiobook_chapters","spotify_get_audiobooks","spotify_get_categories","spotify_get_current_user","spotify_get_currently_playing","spotify_get_devices","spotify_get_episode","spotify_get_episodes","spotify_get_followed_artists","spotify_get_markets","spotify_get_new_releases","spotify_get_playback_state","spotify_get_playlist","spotify_get_playlist_cover","spotify_get_playlist_tracks","spotify_get_queue","spotify_get_recently_played","spotify_get_saved_albums","spotify_get_saved_audiobooks","spotify_get_saved_episodes","spotify_get_saved_shows","spotify_get_saved_tracks","spotify_get_show","spotify_get_show_episodes","spotify_get_shows","spotify_get_top_artists","spotify_get_top_tracks","spotify_get_track","spotify_get_tracks","spotify_get_user_playlists","spotify_get_user_profile","spotify_pause","spotify_play","spotify_remove_saved_albums","spotify_remove_saved_audiobooks","spotify_remove_saved_episodes","spotify_remove_saved_shows","spotify_remove_saved_tracks","spotify_remove_tracks_from_playlist","spotify_reorder_playlist_items","spotify_replace_playlist_items","spotify_save_albums","spotify_save_audiobooks","spotify_save_episodes","spotify_save_shows","spotify_save_tracks","spotify_search","spotify_seek","spotify_set_repeat","spotify_set_shuffle","spotify_set_volume","spotify_skip_next","spotify_skip_previous","spotify_transfer_playback","spotify_unfollow_artists","spotify_unfollow_playlist","spotify_update_playlist","sqs_cancel_message_move_task","sqs_change_message_visibility","sqs_change_message_visibility_batch","sqs_create_queue","sqs_delete_message","sqs_delete_message_batch","sqs_delete_queue","sqs_get_queue_attributes","sqs_get_queue_url","sqs_list_dead_letter_source_queues","sqs_list_message_move_tasks","sqs_list_queue_tags","sqs_list_queues","sqs_purge_queue","sqs_receive_message","sqs_send","sqs_send_message_batch","sqs_set_queue_attributes","sqs_start_message_move_task","sqs_tag_queue","sqs_untag_queue","square_batch_retrieve_inventory_counts","square_cancel_invoice","square_cancel_payment","square_complete_payment","square_create_catalog_image","square_create_customer","square_create_invoice","square_create_order","square_create_payment","square_delete_catalog_object","square_delete_customer","square_delete_invoice","square_get_catalog_object","square_get_customer","square_get_invoice","square_get_location","square_get_order","square_get_payment","square_get_refund","square_list_catalog","square_list_customers","square_list_invoices","square_list_locations","square_list_payments","square_list_refunds","square_pay_order","square_publish_invoice","square_refund_payment","square_search_catalog_objects","square_search_customers","square_search_invoices","square_search_orders","square_update_customer","square_upsert_catalog_object","ssh_check_command_exists","ssh_check_file_exists","ssh_create_directory","ssh_delete_file","ssh_download_file","ssh_download_file_v2","ssh_execute_command","ssh_execute_script","ssh_get_system_info","ssh_list_directory","ssh_move_rename","ssh_read_file_content","ssh_upload_file","ssh_write_file_content","ssm_cancel_command","ssm_delete_parameter","ssm_describe_automation_executions","ssm_describe_instance_information","ssm_describe_instance_patch_states","ssm_describe_instance_patches","ssm_describe_parameters","ssm_get_automation_execution","ssm_get_command_invocation","ssm_get_document","ssm_get_parameter","ssm_get_parameters","ssm_get_parameters_by_path","ssm_list_command_invocations","ssm_list_commands","ssm_list_compliance_items","ssm_list_compliance_summaries","ssm_list_documents","ssm_put_parameter","ssm_send_command","ssm_start_automation_execution","ssm_stop_automation_execution","stagehand_agent","stagehand_extract","stripe_cancel_payment_intent","stripe_cancel_subscription","stripe_capture_charge","stripe_capture_payment_intent","stripe_confirm_payment_intent","stripe_create_charge","stripe_create_customer","stripe_create_invoice","stripe_create_payment_intent","stripe_create_price","stripe_create_product","stripe_create_subscription","stripe_delete_customer","stripe_delete_invoice","stripe_delete_product","stripe_finalize_invoice","stripe_list_charges","stripe_list_customers","stripe_list_events","stripe_list_invoices","stripe_list_payment_intents","stripe_list_prices","stripe_list_products","stripe_list_subscriptions","stripe_pay_invoice","stripe_resume_subscription","stripe_retrieve_charge","stripe_retrieve_customer","stripe_retrieve_event","stripe_retrieve_invoice","stripe_retrieve_payment_intent","stripe_retrieve_price","stripe_retrieve_product","stripe_retrieve_subscription","stripe_search_charges","stripe_search_customers","stripe_search_invoices","stripe_search_payment_intents","stripe_search_prices","stripe_search_products","stripe_search_subscriptions","stripe_send_invoice","stripe_update_charge","stripe_update_customer","stripe_update_invoice","stripe_update_payment_intent","stripe_update_price","stripe_update_product","stripe_update_subscription","stripe_void_invoice","sts_assume_role","sts_assume_role_with_saml","sts_assume_role_with_web_identity","sts_get_access_key_info","sts_get_caller_identity","sts_get_session_token","stt_assemblyai","stt_assemblyai_v2","stt_deepgram","stt_deepgram_v2","stt_elevenlabs","stt_elevenlabs_v2","stt_gemini","stt_gemini_v2","stt_whisper","stt_whisper_v2","supabase_count","supabase_delete","supabase_get_row","supabase_insert","supabase_introspect","supabase_invoke_function","supabase_query","supabase_rpc","supabase_storage_copy","supabase_storage_create_bucket","supabase_storage_create_signed_upload_url","supabase_storage_create_signed_url","supabase_storage_delete","supabase_storage_delete_bucket","supabase_storage_download","supabase_storage_empty_bucket","supabase_storage_get_public_url","supabase_storage_list","supabase_storage_list_buckets","supabase_storage_move","supabase_storage_update_bucket","supabase_storage_upload","supabase_text_search","supabase_update","supabase_upsert","supabase_vector_search","table_batch_insert_rows","table_create","table_delete_row","table_delete_rows_by_filter","table_get_row","table_get_schema","table_insert_row","table_list","table_query_rows","table_query_rows_v2","table_update_row","table_update_rows_by_filter","table_upsert_row","tailscale_authorize_device","tailscale_create_auth_key","tailscale_delete_auth_key","tailscale_delete_device","tailscale_delete_user","tailscale_expire_device_key","tailscale_get_acl","tailscale_get_auth_key","tailscale_get_device","tailscale_get_device_routes","tailscale_get_dns_preferences","tailscale_get_dns_searchpaths","tailscale_list_auth_keys","tailscale_list_devices","tailscale_list_dns_nameservers","tailscale_list_users","tailscale_set_acl","tailscale_set_device_routes","tailscale_set_device_tags","tailscale_set_dns_nameservers","tailscale_set_dns_preferences","tailscale_set_dns_searchpaths","tailscale_suspend_user","tailscale_update_device_key","tavily_crawl","tavily_extract","tavily_map","tavily_search","telegram_copy_message","telegram_delete_message","telegram_edit_message_text","telegram_forward_message","telegram_get_chat","telegram_get_chat_member","telegram_message","telegram_pin_message","telegram_send_animation","telegram_send_audio","telegram_send_chat_action","telegram_send_contact","telegram_send_document","telegram_send_location","telegram_send_photo","telegram_send_poll","telegram_send_video","telegram_set_message_reaction","telegram_unpin_message","temporal_cancel_workflow","temporal_count_workflows","temporal_create_schedule","temporal_delete_schedule","temporal_describe_schedule","temporal_describe_task_queue","temporal_describe_workflow","temporal_get_workflow_history","temporal_list_schedules","temporal_list_workflows","temporal_pause_schedule","temporal_query_workflow","temporal_reset_workflow","temporal_signal_with_start","temporal_signal_workflow","temporal_start_workflow","temporal_terminate_workflow","temporal_trigger_schedule","temporal_unpause_schedule","temporal_update_workflow","textract_analyze_expense","textract_analyze_id","textract_parser","textract_parser_v2","thinking_tool","thrive_add_audience_managers","thrive_add_audience_members","thrive_add_user_tags","thrive_create_assignment","thrive_create_audience","thrive_create_completion","thrive_create_user","thrive_delete_assignment","thrive_delete_audience","thrive_delete_user","thrive_get_activity","thrive_get_assignment","thrive_get_audience","thrive_get_completion","thrive_get_content","thrive_get_cpd_category","thrive_get_cpd_entry","thrive_get_cpd_requirement","thrive_get_enrolment","thrive_get_skill_levels","thrive_get_tag","thrive_get_user_by_id","thrive_get_user_by_ref","thrive_list_assignments","thrive_list_audience_managers","thrive_list_audience_members","thrive_list_audiences","thrive_list_completions","thrive_list_enrolments","thrive_list_tags","thrive_query_activities","thrive_query_content","thrive_query_cpd_categories","thrive_query_cpd_entries","thrive_query_cpd_requirements","thrive_query_cpd_user_summaries","thrive_remove_audience_manager","thrive_remove_audience_member","thrive_remove_user_tags","thrive_replace_audience_managers","thrive_replace_audience_members","thrive_search_users","thrive_suspend_user","thrive_update_assignment","thrive_update_audience","thrive_update_user","thrive_update_user_skills","tiktok_get_post_status","tiktok_get_user","tiktok_list_videos","tiktok_query_videos","tiktok_upload_video_draft","tinybird_append_datasource","tinybird_delete_datasource_rows","tinybird_events","tinybird_get_job","tinybird_query","tinybird_query_pipe","tinybird_truncate_datasource","tinyfish_cancel_run","tinyfish_fetch","tinyfish_get_run","tinyfish_list_profiles","tinyfish_list_runs","tinyfish_list_vault_items","tinyfish_run","tinyfish_run_async","tinyfish_search","trello_add_checklist","trello_add_checklist_item","trello_add_comment","trello_add_label","trello_add_member","trello_create_board","trello_create_card","trello_create_list","trello_delete_card","trello_get_actions","trello_get_board","trello_get_card","trello_list_cards","trello_list_lists","trello_list_members","trello_remove_label","trello_remove_member","trello_search","trello_update_card","trello_update_checklist_item","trello_update_list","trigger_dev_activate_schedule","trigger_dev_add_run_tags","trigger_dev_batch_trigger_task","trigger_dev_cancel_run","trigger_dev_complete_waitpoint_token","trigger_dev_create_env_var","trigger_dev_create_schedule","trigger_dev_create_waitpoint_token","trigger_dev_deactivate_schedule","trigger_dev_delete_env_var","trigger_dev_delete_schedule","trigger_dev_execute_query","trigger_dev_get_batch","trigger_dev_get_batch_results","trigger_dev_get_deployment","trigger_dev_get_env_var","trigger_dev_get_latest_deployment","trigger_dev_get_query_schema","trigger_dev_get_queue","trigger_dev_get_run","trigger_dev_get_run_events","trigger_dev_get_run_result","trigger_dev_get_run_trace","trigger_dev_get_schedule","trigger_dev_get_waitpoint_token","trigger_dev_import_env_vars","trigger_dev_list_deployments","trigger_dev_list_env_vars","trigger_dev_list_queues","trigger_dev_list_runs","trigger_dev_list_schedules","trigger_dev_list_timezones","trigger_dev_list_waitpoint_tokens","trigger_dev_override_queue_concurrency","trigger_dev_pause_queue","trigger_dev_promote_deployment","trigger_dev_replay_run","trigger_dev_reschedule_run","trigger_dev_reset_queue_concurrency","trigger_dev_resume_queue","trigger_dev_trigger_task","trigger_dev_update_env_var","trigger_dev_update_run_metadata","trigger_dev_update_schedule","tts_azure","tts_cartesia","tts_deepgram","tts_elevenlabs","tts_google","tts_openai","tts_playht","twilio_send_sms","twilio_voice_get_recording","twilio_voice_list_calls","twilio_voice_make_call","typeform_create_form","typeform_delete_form","typeform_files","typeform_get_form","typeform_insights","typeform_list_forms","typeform_responses","typeform_update_form","upstash_redis_command","upstash_redis_delete","upstash_redis_exists","upstash_redis_expire","upstash_redis_get","upstash_redis_hget","upstash_redis_hgetall","upstash_redis_hset","upstash_redis_incr","upstash_redis_incrby","upstash_redis_keys","upstash_redis_lpush","upstash_redis_lrange","upstash_redis_set","upstash_redis_setnx","upstash_redis_ttl","uptimerobot_create_alert_contact","uptimerobot_create_maintenance_window","uptimerobot_create_monitor","uptimerobot_create_psp","uptimerobot_delete_alert_contact","uptimerobot_delete_maintenance_window","uptimerobot_delete_monitor","uptimerobot_delete_psp","uptimerobot_get_account","uptimerobot_get_alert_contact","uptimerobot_get_incident","uptimerobot_get_maintenance_window","uptimerobot_get_monitor","uptimerobot_get_psp","uptimerobot_list_alert_contacts","uptimerobot_list_incidents","uptimerobot_list_maintenance_windows","uptimerobot_list_monitors","uptimerobot_list_psps","uptimerobot_pause_monitor","uptimerobot_start_monitor","uptimerobot_update_maintenance_window","uptimerobot_update_monitor","uptimerobot_update_psp","vanta_download_document_file","vanta_get_control","vanta_get_document","vanta_get_framework","vanta_get_person","vanta_get_policy","vanta_get_risk_scenario","vanta_get_test","vanta_get_vendor","vanta_get_vulnerable_asset","vanta_list_control_documents","vanta_list_control_tests","vanta_list_controls","vanta_list_document_uploads","vanta_list_documents","vanta_list_framework_controls","vanta_list_frameworks","vanta_list_monitored_computers","vanta_list_people","vanta_list_policies","vanta_list_risk_scenarios","vanta_list_test_entities","vanta_list_tests","vanta_list_vendors","vanta_list_vulnerabilities","vanta_list_vulnerability_remediations","vanta_list_vulnerable_assets","vanta_submit_document","vanta_upload_document_file","vercel_add_domain","vercel_add_project_domain","vercel_cancel_deployment","vercel_create_alias","vercel_create_check","vercel_create_deployment","vercel_create_dns_record","vercel_create_edge_config","vercel_create_env_var","vercel_create_project","vercel_create_webhook","vercel_delete_alias","vercel_delete_deployment","vercel_delete_dns_record","vercel_delete_domain","vercel_delete_edge_config","vercel_delete_env_var","vercel_delete_project","vercel_delete_webhook","vercel_get_alias","vercel_get_check","vercel_get_deployment","vercel_get_deployment_events","vercel_get_domain","vercel_get_domain_config","vercel_get_edge_config","vercel_get_edge_config_items","vercel_get_env_vars","vercel_get_project","vercel_get_team","vercel_get_user","vercel_get_webhook","vercel_list_aliases","vercel_list_checks","vercel_list_deployment_files","vercel_list_deployments","vercel_list_dns_records","vercel_list_domains","vercel_list_edge_configs","vercel_list_project_domains","vercel_list_projects","vercel_list_team_members","vercel_list_teams","vercel_list_webhooks","vercel_pause_project","vercel_promote_deployment","vercel_remove_project_domain","vercel_rerequest_check","vercel_unpause_project","vercel_update_check","vercel_update_dns_record","vercel_update_edge_config_items","vercel_update_env_var","vercel_update_project","vercel_update_project_domain","vercel_verify_project_domain","video_falai","video_luma","video_minimax","video_runway","video_veo","vision_tool","vision_tool_v2","wealthbox_read_contact","wealthbox_read_note","wealthbox_read_task","wealthbox_write_contact","wealthbox_write_note","wealthbox_write_task","webflow_create_item","webflow_delete_item","webflow_get_item","webflow_list_items","webflow_update_item","webhook_request","whatsapp_get_media","whatsapp_mark_read","whatsapp_send_interactive","whatsapp_send_media","whatsapp_send_message","whatsapp_send_reaction","whatsapp_send_template","whatsapp_upload_media","wikipedia_content","wikipedia_random","wikipedia_search","wikipedia_summary","windchill_check_in_document","windchill_check_in_documents","windchill_check_out_document","windchill_check_out_documents","windchill_create_document","windchill_create_documents","windchill_delete_document","windchill_delete_documents","windchill_download_attachment","windchill_download_primary_content","windchill_get_document","windchill_get_document_structure","windchill_get_primary_content","windchill_get_valid_state_transitions","windchill_list_attachments","windchill_list_documents","windchill_revise_document","windchill_revise_documents","windchill_set_lifecycle_state","windchill_undo_check_out_document","windchill_undo_check_out_documents","windchill_update_common_properties","windchill_update_document","windchill_update_document_security_labels","windchill_update_documents","windchill_upload_attachments","windchill_upload_primary_content","wiza_company_enrichment","wiza_get_credits","wiza_individual_reveal","wiza_prospect_search","wordpress_create_category","wordpress_create_comment","wordpress_create_page","wordpress_create_post","wordpress_create_tag","wordpress_delete_category","wordpress_delete_comment","wordpress_delete_media","wordpress_delete_page","wordpress_delete_post","wordpress_delete_tag","wordpress_get_category","wordpress_get_current_user","wordpress_get_media","wordpress_get_page","wordpress_get_post","wordpress_get_tag","wordpress_get_user","wordpress_list_categories","wordpress_list_comments","wordpress_list_media","wordpress_list_pages","wordpress_list_posts","wordpress_list_tags","wordpress_list_users","wordpress_search_content","wordpress_update_category","wordpress_update_comment","wordpress_update_page","wordpress_update_post","wordpress_update_tag","wordpress_upload_media","workday_assign_onboarding","workday_change_job","workday_create_prehire","workday_get_compensation","workday_get_organizations","workday_get_worker","workday_hire_employee","workday_list_workers","workday_terminate_worker","workday_update_worker","workflow_executor","x_create_bookmark","x_create_tweet","x_delete_bookmark","x_delete_tweet","x_get_blocking","x_get_bookmarks","x_get_followers","x_get_following","x_get_liked_tweets","x_get_liking_users","x_get_me","x_get_personalized_trends","x_get_quote_tweets","x_get_retweeted_by","x_get_trends_by_woeid","x_get_tweets_by_ids","x_get_usage","x_get_user_mentions","x_get_user_timeline","x_get_user_tweets","x_hide_reply","x_manage_block","x_manage_follow","x_manage_like","x_manage_mute","x_manage_retweet","x_read","x_search","x_search_tweets","x_search_users","x_user","x_write","youtube_channel_info","youtube_channel_playlists","youtube_channel_videos","youtube_comments","youtube_playlist_items","youtube_search","youtube_trending","youtube_video_categories","youtube_video_details","zendesk_autocomplete_organizations","zendesk_create_organization","zendesk_create_organizations_bulk","zendesk_create_ticket","zendesk_create_tickets_bulk","zendesk_create_user","zendesk_create_users_bulk","zendesk_delete_organization","zendesk_delete_ticket","zendesk_delete_user","zendesk_get_current_user","zendesk_get_organization","zendesk_get_organizations","zendesk_get_ticket","zendesk_get_tickets","zendesk_get_user","zendesk_get_users","zendesk_merge_tickets","zendesk_search","zendesk_search_count","zendesk_search_users","zendesk_update_organization","zendesk_update_ticket","zendesk_update_tickets_bulk","zendesk_update_user","zendesk_update_users_bulk","zep_add_messages","zep_add_user","zep_create_thread","zep_delete_thread","zep_get_context","zep_get_messages","zep_get_threads","zep_get_user","zep_get_user_threads","zerobounce_get_credits","zerobounce_verify_email","zoho_desk_add_comment","zoho_desk_get_attachment","zoho_desk_get_contact","zoho_desk_get_thread","zoho_desk_get_ticket","zoho_desk_list_comments","zoho_desk_list_organizations","zoho_desk_list_threads","zoho_desk_list_tickets","zoho_desk_update_ticket","zoom_create_meeting","zoom_delete_meeting","zoom_delete_recording","zoom_get_meeting","zoom_get_meeting_invitation","zoom_get_meeting_recordings","zoom_list_meetings","zoom_list_past_participants","zoom_list_recordings","zoom_update_meeting","zoominfo_enrich_companies","zoominfo_enrich_contacts","zoominfo_search_companies","zoominfo_search_contacts","zoominfo_search_intent","zoominfo_search_news"]' ) export default toolIds diff --git a/apps/sim/tools/generated/tool-metadata.ts b/apps/sim/tools/generated/tool-metadata.ts index dacfa485f2a..204ce6efcfe 100644 --- a/apps/sim/tools/generated/tool-metadata.ts +++ b/apps/sim/tools/generated/tool-metadata.ts @@ -3,7 +3,7 @@ /** Serializable metadata for every built-in tool, keyed by tool id. */ const toolMetadata: Record = JSON.parse( - '{"a2a_cancel_task":{"id":"a2a_cancel_task","name":"A2A Cancel Task","description":"Request cancellation of an in-progress A2A task.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The task ID to cancel"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}},"hostedApiKey":"none"},"a2a_get_agent_card":{"id":"a2a_get_agent_card","name":"A2A Get Agent Card","description":"Fetch the Agent Card (discovery document) for an external A2A agent.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}},"hostedApiKey":"none"},"a2a_get_task":{"id":"a2a_get_task","name":"A2A Get Task","description":"Retrieve the current state and result of an A2A task.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The task ID to retrieve"},"historyLength":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of history messages to include"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}},"hostedApiKey":"none"},"a2a_send_message":{"id":"a2a_send_message","name":"A2A Send Message","description":"Send a message to an external A2A agent and return its response.","version":"1.0.0","params":{"agentUrl":{"type":"string","required":true,"visibility":"user-only","description":"The A2A agent endpoint URL"},"message":{"type":"string","required":true,"visibility":"user-or-llm","description":"The message text to send"},"data":{"type":"json","required":false,"visibility":"user-or-llm","description":"Optional structured JSON data to attach"},"files":{"type":"json","required":false,"visibility":"user-or-llm","description":"Optional files to attach"},"taskId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Existing task ID to continue"},"contextId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Conversation context ID to continue"},"apiKey":{"type":"string","required":false,"visibility":"user-only","description":"API key for authentication (if required)"}},"hostedApiKey":"none"},"affinity_batch_update_entity_fields":{"id":"affinity_batch_update_entity_fields","name":"Affinity Batch Update Entity Fields","description":"Write up to 100 non-list field values on one company or person in a single request.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to write the fields on: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"updates":{"type":"json","required":true,"visibility":"user-or-llm","description":"Up to 100 field updates as [{\\"id\\":\\"\\",\\"value\\":{\\"type\\":\\"…\\",\\"data\\":…}}], using the same value shapes as a single field update"}},"hostedApiKey":"none"},"affinity_batch_update_list_entry_fields":{"id":"affinity_batch_update_list_entry_fields","name":"Affinity Batch Update List Entry Fields","description":"Write up to 100 field values on one list row in a single request. Requires the \\"Export data from Lists\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"updates":{"type":"json","required":true,"visibility":"user-or-llm","description":"Up to 100 field updates as [{\\"id\\":\\"\\",\\"value\\":{\\"type\\":\\"…\\",\\"data\\":…}}], using the same value shapes as a single field update"}},"hostedApiKey":"none"},"affinity_create_list":{"id":"affinity_create_list","name":"Affinity Create List","description":"Create a list. Its type fixes which entities it can hold, and the API key holder becomes its creator and owner.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the new list"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Entity kind the list holds: company, opportunity, or person"},"isPublic":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether everyone in the organization can see the list"}},"hostedApiKey":"none"},"affinity_create_list_field_dropdown_option":{"id":"affinity_create_list_field_dropdown_option","name":"Affinity Create List Field Dropdown Option","description":"Add a selectable option to a dropdown field on a list. A ranked or status option also needs a rank and a color.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Kind of option to create, matching the field. dropdown takes only a label; ranked-dropdown also requires rank and color; status-dropdown additionally requires a status category. Sending a field the kind does not accept is rejected"},"text":{"type":"string","required":true,"visibility":"user-or-llm","description":"The option label"},"rank":{"type":"number","required":false,"visibility":"user-or-llm","description":"Sort order. Required on a ranked-dropdown or status-dropdown option"},"color":{"type":"string","required":false,"visibility":"user-or-llm","description":"Option color: white, gray, blue, green, purple, orange, or red. Required on a ranked-dropdown or status-dropdown option"},"statusCategory":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pipeline meaning of the option: open, won, lost, or on-hold. Status-dropdown options only"},"winRate":{"type":"number","required":false,"visibility":"user-or-llm","description":"Expected win rate of the status. Status-dropdown options only"}},"hostedApiKey":"none"},"affinity_create_merge":{"id":"affinity_create_merge","name":"Affinity Create Merge","description":"Fold a duplicate company or person into the record you are keeping. The merge runs asynchronously — poll the returned task to see it finish. Requires the \\"Manage duplicates\\" permission and an admin role.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to merge: companies or persons"},"primaryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to keep"},"duplicateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the duplicate record to fold in"}},"hostedApiKey":"none"},"affinity_create_note":{"id":"affinity_create_note","name":"Affinity Create Note","description":"Write a note — attached to companies, persons, and opportunities, anchored to a meeting, call, or chat message, or posted as a reply to an existing note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Note shape: entities to attach it to records, interaction to anchor it to a meeting, call, or chat message, or user-reply to reply to a note"},"html":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note body as HTML"},"companyIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Companies to attach the note to, e.g. [1, 2]. Not used on a reply"},"personIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Persons to attach the note to, e.g. [1, 2]. Not used on a reply"},"opportunityIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Opportunities to attach the note to, e.g. [1, 2]. Not used on a reply"},"interactionId":{"type":"string","required":false,"visibility":"user-or-llm","description":"The interaction to anchor the note to. Required for an interaction note"},"interactionType":{"type":"string","required":false,"visibility":"user-or-llm","description":"Kind of the anchoring interaction: meeting, call, or chat-message. Required for an interaction note"},"parentId":{"type":"string","required":false,"visibility":"user-or-llm","description":"The note being replied to. Required for a user-reply note"},"creatorId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Attribute the note to another internal person. Defaults to the API key holder"},"createdAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"Backdate the note to this ISO 8601 timestamp"}},"hostedApiKey":"none"},"affinity_create_reminder":{"id":"affinity_create_reminder","name":"Affinity Create Reminder","description":"Create a reminder on one company, person, or opportunity. A recurring reminder resets whenever the chosen signal happens instead of firing once.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"one-time to fire once, or recurring to reset on a signal"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"What the reminder is about: company, person, or opportunity"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company, person, or opportunity"},"dueDate":{"type":"string","required":false,"visibility":"user-or-llm","description":"When the reminder is due, as an ISO 8601 timestamp. Required for a one-time reminder; on a recurring one Affinity computes it from the period when omitted"},"content":{"type":"string","required":false,"visibility":"user-or-llm","description":"What the reminder says"},"ownerId":{"type":"string","required":true,"visibility":"user-or-llm","description":"User the reminder is assigned to. Must be an internal user. The API key holder is recorded as the creator, which is a separate field"},"resetTrigger":{"type":"string","required":false,"visibility":"user-or-llm","description":"What restarts a recurring reminder: interaction, email, or event. Required when the type is recurring"},"periodDays":{"type":"number","required":false,"visibility":"user-or-llm","description":"Days between firings of a recurring reminder. Required when the type is recurring"}},"hostedApiKey":"none"},"affinity_delete_list_field_dropdown_option":{"id":"affinity_delete_list_field_dropdown_option","name":"Affinity Delete List Field Dropdown Option","description":"Permanently delete a dropdown option on a list field. Every list entry currently set to it is cleared, and those values cannot be recovered.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"dropdownOptionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown option ID to delete"}},"hostedApiKey":"none"},"affinity_delete_note":{"id":"affinity_delete_note","name":"Affinity Delete Note","description":"Delete a note you created. Deleting a root note also deletes its replies; deleting a reply removes only that reply.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID to delete"}},"hostedApiKey":"none"},"affinity_get_company":{"id":"affinity_get_company","name":"Affinity Get Company","description":"Look up one company by ID. Field data is returned only for the Field IDs or Field Types asked for.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"companyId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The company ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"}},"hostedApiKey":"none"},"affinity_get_current_user":{"id":"affinity_get_current_user","name":"Affinity Get Current User","description":"Verify an Affinity API key and return the tenant, the user behind the key, and the scopes the grant carries.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"}},"hostedApiKey":"none"},"affinity_get_entity_field_value":{"id":"affinity_get_entity_field_value","name":"Affinity Get Entity Field Value","description":"Read one non-list field value from a company or person.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to read the field from: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to read"}},"hostedApiKey":"none"},"affinity_get_list":{"id":"affinity_get_list","name":"Affinity Get List","description":"Read one list — its name, type, owner, and privacy setting.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"}},"hostedApiKey":"none"},"affinity_get_list_entry":{"id":"affinity_get_list_entry","name":"Affinity Get List Entry","description":"Read one row of a list with its entity. Field data is returned only for the Field IDs or Field Types asked for.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, list, or relationship-intelligence. Mutually exclusive with Field IDs"}},"hostedApiKey":"none"},"affinity_get_list_entry_field":{"id":"affinity_get_list_entry_field","name":"Affinity Get List Entry Field","description":"Read one field value on a list row.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to read"}},"hostedApiKey":"none"},"affinity_get_list_field_dropdown_option":{"id":"affinity_get_list_field_dropdown_option","name":"Affinity Get List Field Dropdown Option","description":"Read one dropdown option on a list field.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"dropdownOptionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown option ID"}},"hostedApiKey":"none"},"affinity_get_merge":{"id":"affinity_get_merge","name":"Affinity Get Merge","description":"Read the status of one company or person merge, including why it failed if it did.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merge to read: companies or persons"},"mergeId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The merge ID"}},"hostedApiKey":"none"},"affinity_get_merge_task":{"id":"affinity_get_merge_task","name":"Affinity Get Merge Task","description":"Read one merge task and how its merges are progressing. Poll this after starting a merge.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merge task to read: companies or persons"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The merge task ID"}},"hostedApiKey":"none"},"affinity_get_note":{"id":"affinity_get_note","name":"Affinity Get Note","description":"Read one note with its body, author, mentions, and attached records.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return, e.g. [\\"repliesCount\\",\\"personsPreview\\",\\"companiesPreview\\",\\"opportunitiesPreview\\"]. Those four fields are omitted unless requested here"}},"hostedApiKey":"none"},"affinity_get_opportunity":{"id":"affinity_get_opportunity","name":"Affinity Get Opportunity","description":"Read one opportunity and the list it belongs to. Its field data lives on the list entry.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"opportunityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The opportunity ID"}},"hostedApiKey":"none"},"affinity_get_person":{"id":"affinity_get_person","name":"Affinity Get Person","description":"Look up one person by ID. Field data is returned only for the Field IDs or Field Types asked for.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"personId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The person ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"}},"hostedApiKey":"none"},"affinity_get_saved_view":{"id":"affinity_get_saved_view","name":"Affinity Get Saved View","description":"Read one saved view — its name, kind, and creation date.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"viewId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The saved view ID"}},"hostedApiKey":"none"},"affinity_get_transcript":{"id":"affinity_get_transcript","name":"Affinity Get Transcript","description":"Read one transcript with its first 100 fragments. Page the fragments endpoint for a longer meeting.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"transcriptId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The transcript ID"}},"hostedApiKey":"none"},"affinity_get_user":{"id":"affinity_get_user","name":"Affinity Get User","description":"Read one internal user. A user and their person record share the same numeric ID, so a person ID works here.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"userId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The user ID, which is also their person ID"}},"hostedApiKey":"none"},"affinity_list_calls":{"id":"affinity_list_calls","name":"Affinity List Calls","description":"Page through logged calls and their participants. Only calls the API key holder can see are returned.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_chat_messages":{"id":"affinity_list_chat_messages","name":"Affinity List Chat Messages","description":"Page through logged chat messages and their participants. Only messages the API key holder can see are returned.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_companies":{"id":"affinity_list_companies","name":"Affinity List Companies","description":"Page through companies. Companies come back without field data unless Field IDs or Field Types asks for it.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the page to these company IDs, e.g. [1, 2, 3]"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_coworker_connections":{"id":"affinity_list_coworker_connections","name":"Affinity List Coworker Connections","description":"Find warm paths into a company through shared work history: who in your Affinity data once worked alongside the people you want to reach. Grouped by target, strongest first.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":true,"visibility":"user-or-llm","description":"Required scope. The only supported filter is target.currentCompany.id, e.g. \\"target.currentCompany.id=123\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of targets to return per page, 1-50. Defaults to 20"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_emails":{"id":"affinity_list_emails","name":"Affinity List Emails","description":"Page through email metadata — subject, participants, and timestamps. Affinity never exposes email bodies through the API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_entity_field_values":{"id":"affinity_list_entity_field_values","name":"Affinity List Entity Field Values","description":"Page through a company\'s or person\'s non-list field values. List fields are not returned here — read those through the list entry.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to read field values from: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field IDs. Mutually exclusive with Field Types"},"types":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field categories: enriched, global, relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 20"}},"hostedApiKey":"none"},"affinity_list_entity_list_entries":{"id":"affinity_list_entity_list_entries","name":"Affinity List Entity List Entries","description":"Page through a company\'s or person\'s rows across every list, each carrying that list\'s field values and when the entity was added.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to look up the rows of: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_entity_lists":{"id":"affinity_list_entity_lists","name":"Affinity List Entity Lists","description":"List every list a company or person appears on that the caller can view.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to look up the lists of: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_entity_notes":{"id":"affinity_list_entity_notes","name":"Affinity List Entity Notes","description":"List the notes relevant to one company, person, or opportunity — directly attached notes plus notes reaching it through its people and meetings.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity the notes hang off: companies, persons, or opportunities"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company, person, or opportunity"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_entity_relationships":{"id":"affinity_list_entity_relationships","name":"Affinity List Entity Relationships","description":"List who knows a company or person, scored 0.0 to 1.0 by how much the two actually interact. Strongest first by default.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to look up relationships for: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on interactionScore only, e.g. \\"interactionScore>=0.5\\""},"orderBy":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order: [\\"interactionScore\\"] for weakest first, [\\"-interactionScore\\"] for strongest first (the default)"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_field_dropdown_options":{"id":"affinity_list_field_dropdown_options","name":"Affinity List Field Dropdown Options","description":"List the selectable options on a dropdown or ranked-dropdown company or person field. Writing such a field needs the option ID, not its text.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which field family the field belongs to: companies or persons"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown or ranked-dropdown field ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_field_metadata":{"id":"affinity_list_field_metadata","name":"Affinity List Field Metadata","description":"List the non-list company or person fields, with the value type, filter operators, and sort support of each. Start here to find the Field IDs the read and write tools take.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which fields to describe: companies or persons"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return: [\\"filterability\\",\\"sortability\\"]. Both are omitted unless requested here"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on name only, e.g. \\"name=~Status\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_field_value_changes":{"id":"affinity_list_field_value_changes","name":"Affinity List Field Value Changes","description":"Page through field value changes across the whole workspace. Built for delta sync: follow nextCursor to the end of a run, then resume from the last cursor next time.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over field.id, listEntry.id, changer.id, changedAt, or actionType. Resume a sync with e.g. \\"changedAt>2026-06-01T12:00:00Z\\""},"orderBy":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order: [\\"changedAt\\"] for oldest first (the default), [\\"-changedAt\\"] for newest first"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_investor_executive_connections":{"id":"affinity_list_investor_executive_connections","name":"Affinity List Investor Executive Connections","description":"Find warm paths into a company through investment history: which investors in your Affinity data backed a company the people you want to reach once led. Grouped by target, strongest first.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":true,"visibility":"user-or-llm","description":"Required scope. The only supported filter is target.currentCompany.id, e.g. \\"target.currentCompany.id=123\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of targets to return per page, 1-50. Defaults to 20"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_list_entries":{"id":"affinity_list_list_entries","name":"Affinity List List Entries","description":"Page through the rows of a list. Rows come back without field data unless Field IDs or Field Types asks for it.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, list, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_list_entry_field_value_changes":{"id":"affinity_list_list_entry_field_value_changes","name":"Affinity List List Entry Field Value Changes","description":"Page through the history of one list row — who changed which field, when, and to what.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over field.id, changer.id, changedAt, or actionType, e.g. \\"field.id=field-1234\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_list_entry_fields":{"id":"affinity_list_list_entry_fields","name":"Affinity List List Entry Fields","description":"Page through every field value on one list row, including the list-specific columns. All fields are returned unless narrowed.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field IDs. Mutually exclusive with Field Types"},"types":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict to these field categories: enriched, global, list, relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 20"}},"hostedApiKey":"none"},"affinity_list_list_field_dropdown_options":{"id":"affinity_list_list_field_dropdown_options","name":"Affinity List List Field Dropdown Options","description":"List the selectable options on a dropdown, ranked-dropdown, or status-dropdown field of a list.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_list_fields":{"id":"affinity_list_list_fields","name":"Affinity List List Fields","description":"List the fields available on one list, including its list-specific columns. Use these Field IDs when reading or writing list entries.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return: [\\"filterability\\",\\"sortability\\"]. Both are omitted unless requested here"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on name only, e.g. \\"name=~Stage\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_lists":{"id":"affinity_list_lists","name":"Affinity List Lists","description":"Page through the lists in the organization that the caller can view.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"term":{"type":"string","required":false,"visibility":"user-or-llm","description":"Case-insensitive substring match on the list name"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_meetings":{"id":"affinity_list_meetings","name":"Affinity List Meetings","description":"Page through past and upcoming meetings with their organizer and attendees.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_merge_tasks":{"id":"affinity_list_merge_tasks","name":"Affinity List Merge Tasks","description":"Page through merge tasks, each summarizing how many of its merges are in progress, succeeded, or failed.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merge tasks to list: companies or persons"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression. This endpoint filters on status only, e.g. \\"status=in-progress\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_merges":{"id":"affinity_list_merges","name":"Affinity List Merges","description":"Page through the company or person merges the organization has run, with the status and the records involved in each.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which merges to list: companies or persons"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over status or taskId, e.g. \\"status=failed\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_note_attached_companies":{"id":"affinity_list_note_attached_companies","name":"Affinity List Note Attached Companies","description":"List the companies directly attached to one note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_note_attached_opportunities":{"id":"affinity_list_note_attached_opportunities","name":"Affinity List Note Attached Opportunities","description":"List the opportunities directly attached to one note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_note_attached_persons":{"id":"affinity_list_note_attached_persons","name":"Affinity List Note Attached Persons","description":"List the persons directly attached to one note.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_note_replies":{"id":"affinity_list_note_replies","name":"Affinity List Note Replies","description":"Page through the replies on one note, including AI Notetaker replies.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID whose replies to read"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_notes":{"id":"affinity_list_notes","name":"Affinity List Notes","description":"Page through every note the caller can see. Replies are excluded.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"includes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Extra properties to return, e.g. [\\"repliesCount\\",\\"personsPreview\\",\\"companiesPreview\\",\\"opportunitiesPreview\\"]. Those four fields are omitted unless requested here"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_opportunities":{"id":"affinity_list_opportunities","name":"Affinity List Opportunities","description":"Page through opportunities. Field data lives on the list entry, not here — read it through the list or saved view the opportunity belongs to.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the page to these opportunity IDs, e.g. [1, 2, 3]"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_persons":{"id":"affinity_list_persons","name":"Affinity List Persons","description":"Page through persons. Persons come back without field data unless Field IDs or Field Types asks for it.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the page to these person IDs, e.g. [1, 2, 3]"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_reminders":{"id":"affinity_list_reminders","name":"Affinity List Reminders","description":"Page through the reminders the caller can see. Filter by status to surface what is overdue.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_saved_view_entries":{"id":"affinity_list_saved_view_entries","name":"Affinity List Saved View Entries","description":"Page through the rows of a saved view. The view\'s own filters and columns decide which rows and which field data come back.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"viewId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The saved view ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_saved_views":{"id":"affinity_list_saved_views","name":"Affinity List Saved Views","description":"List the saved views on a list that the caller can view.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_list_transcript_fragments":{"id":"affinity_list_transcript_fragments","name":"Affinity List Transcript Fragments","description":"Page through everything said in a meeting, segment by segment with the speaker.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"transcriptId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The transcript ID"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_transcripts":{"id":"affinity_list_transcripts","name":"Affinity List Transcripts","description":"Page through meeting transcript metadata. Read one transcript to get what was actually said.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression, e.g. \\"createdAt>=2026-01-01\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_list_users":{"id":"affinity_list_users","name":"Affinity List Users","description":"Page through the internal users in the organization. Email addresses and roles are returned only to callers with the \\"Manage Users\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"term":{"type":"string","required":false,"visibility":"user-or-llm","description":"Case-insensitive match across first name, last name, and primary email"},"filter":{"type":"string","required":false,"visibility":"user-or-llm","description":"Affinity Filtering Language expression over id or status, e.g. \\"status=active\\""},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_search_companies":{"id":"affinity_search_companies","name":"Affinity Search Companies","description":"Search companies by filters, sorts, and a free-text term. Requires the \\"Export All Organizations directory\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Filter group as {operator: \\"and\\"|\\"or\\", filters: [...]}, at most 50 leaves. Each leaf is {valueType, fieldId, operator, value}, and a leaf may itself be a nested group"},"searchTerm":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-text term matched against the searchable fields. At least 3 characters"},"searchFieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs the search term is matched against. Defaults to the searchable fields"},"sorts":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order as [{fieldId, direction: \\"asc\\"|\\"desc\\", attributeId?}], up to 5, applied in order"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_search_files":{"id":"affinity_search_files","name":"Affinity Search Files","description":"Search files by keyword, ordered by relevance. Narrow to specific files or to one company, or leave both unset to search the whole account.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"prompt":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to search for. Between 3 and 500 characters"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the search to these file IDs. Cannot be combined with Company ID"},"companyId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Restrict the search to one company\'s files. Cannot be combined with file IDs"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of files to return, 1-100. Defaults to 20"}},"hostedApiKey":"none"},"affinity_search_list_entries":{"id":"affinity_search_list_entries","name":"Affinity Search List Entries","description":"Search the rows of one list by filters, sorts, and a free-text term. Requires the \\"Export data from Lists\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID to search"},"filters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Filter group as {operator: \\"and\\"|\\"or\\", filters: [...]}, at most 50 leaves. Each leaf is {valueType, fieldId, operator, value}, and a leaf may itself be a nested group"},"searchTerm":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-text term matched against the searchable fields. At least 3 characters"},"searchFieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs the search term is matched against. Defaults to the searchable fields"},"sorts":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order as [{fieldId, direction: \\"asc\\"|\\"desc\\", attributeId?}], up to 5, applied in order"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, list, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_search_notes":{"id":"affinity_search_notes","name":"Affinity Search Notes","description":"Search notes by keyword, ordered by relevance. Narrow to specific notes or to one company, or leave both unset to search the whole account.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"prompt":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to search for. Between 3 and 500 characters"},"ids":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the search to these note IDs. Cannot be combined with Company ID"},"companyId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Restrict the search to one company\'s notes. Cannot be combined with note IDs"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of notes to return, 1-100. Defaults to 20"}},"hostedApiKey":"none"},"affinity_search_persons":{"id":"affinity_search_persons","name":"Affinity Search Persons","description":"Search persons by filters, sorts, and a free-text term. Requires the \\"Export All People directory\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"filters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Filter group as {operator: \\"and\\"|\\"or\\", filters: [...]}, at most 50 leaves. Each leaf is {valueType, fieldId, operator, value}, and a leaf may itself be a nested group"},"searchTerm":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-text term matched against the searchable fields. At least 3 characters"},"searchFieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs the search term is matched against. Defaults to the searchable fields"},"sorts":{"type":"json","required":false,"visibility":"user-or-llm","description":"Sort order as [{fieldId, direction: \\"asc\\"|\\"desc\\", attributeId?}], up to 5, applied in order"},"fieldIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field IDs to return values for, e.g. [\\"affinity-data-location\\"]. Mutually exclusive with Field Types"},"fieldTypes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Field categories to return values for: enriched, global, or relationship-intelligence. Mutually exclusive with Field IDs"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous page, returned as nextCursor or prevCursor"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to return per page, 1-100. Defaults to 100"},"totalCount":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the total size of the collection. Costs an extra query"}},"hostedApiKey":"none"},"affinity_semantic_search":{"id":"affinity_semantic_search","name":"Affinity Semantic Search","description":"Find companies from a description in plain language — industry, technology, stage, or business model. Currently searches companies only.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"prompt":{"type":"string","required":true,"visibility":"user-or-llm","description":"What to look for, in plain language, e.g. \\"climate tech companies in our pipeline\\". Up to 500 characters"},"listIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Restrict the search to companies on these lists, e.g. [1, 2]"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of companies to return, 1-100. Defaults to 100"}},"hostedApiKey":"none"},"affinity_update_entity_field_value":{"id":"affinity_update_entity_field_value","name":"Affinity Update Entity Field Value","description":"Write one non-list field value on a company or person. The value type must match how the field is defined.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"entityType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Which entity to write the field on: companies or persons"},"entityId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of that company or person"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to write"},"value":{"type":"json","required":true,"visibility":"user-or-llm","description":"The new value as {type, data}, where type matches the field\'s value type. Examples: {\\"type\\":\\"text\\",\\"data\\":\\"Series B\\"}, {\\"type\\":\\"number\\",\\"data\\":42}, {\\"type\\":\\"dropdown\\",\\"data\\":{\\"dropdownOptionId\\":7}}, {\\"type\\":\\"person\\",\\"data\\":{\\"id\\":123}}, {\\"type\\":\\"person-multi\\",\\"data\\":[{\\"id\\":123}]}. Pass data as null to clear the field"}},"hostedApiKey":"none"},"affinity_update_list_entry_field":{"id":"affinity_update_list_entry_field","name":"Affinity Update List Entry Field","description":"Write one field value on a list row. Requires the \\"Export data from Lists\\" permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"listEntryId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list entry ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The field ID to write"},"value":{"type":"json","required":true,"visibility":"user-or-llm","description":"The new value as {type, data}, where type matches the field\'s value type. Examples: {\\"type\\":\\"text\\",\\"data\\":\\"Series B\\"}, {\\"type\\":\\"number\\",\\"data\\":42}, {\\"type\\":\\"dropdown\\",\\"data\\":{\\"dropdownOptionId\\":7}}, {\\"type\\":\\"person\\",\\"data\\":{\\"id\\":123}}, {\\"type\\":\\"person-multi\\",\\"data\\":[{\\"id\\":123}]}. Pass data as null to clear the field"}},"hostedApiKey":"none"},"affinity_update_list_field_dropdown_option":{"id":"affinity_update_list_field_dropdown_option","name":"Affinity Update List Field Dropdown Option","description":"Change a dropdown option on a list field. Every field is optional — supply only what should change, and only fields the option\'s kind actually has.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"listId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The list ID"},"fieldId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown field ID on that list"},"dropdownOptionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The dropdown option ID to update"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Replacement option label. Supply at least one field to change"},"rank":{"type":"number","required":false,"visibility":"user-or-llm","description":"Sort order. Required on a ranked-dropdown or status-dropdown option"},"color":{"type":"string","required":false,"visibility":"user-or-llm","description":"Option color: white, gray, blue, green, purple, orange, or red. Required on a ranked-dropdown or status-dropdown option"},"statusCategory":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pipeline meaning of the option: open, won, lost, or on-hold. Status-dropdown options only"},"winRate":{"type":"number","required":false,"visibility":"user-or-llm","description":"Expected win rate of the status. Status-dropdown options only"}},"hostedApiKey":"none"},"affinity_update_note":{"id":"affinity_update_note","name":"Affinity Update Note","description":"Rewrite a note\'s body or replace which records it is attached to. Each list of IDs replaces that association wholesale, an empty list clears it, and omitting one leaves it untouched. A note\'s type cannot be changed.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Affinity API key, sent as a bearer token"},"noteId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note ID to update"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"Replacement note body as HTML"},"companyIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Replacement set of attached companies, e.g. [1, 2]. Send [] to detach every company; omit to leave them unchanged"},"personIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Replacement set of attached persons, e.g. [1, 2]. Send [] to detach every person; omit to leave them unchanged"},"opportunityIds":{"type":"json","required":false,"visibility":"user-or-llm","description":"Replacement set of attached opportunities, e.g. [1, 2]. Send [] to detach every opportunity; omit to leave them unchanged"}},"hostedApiKey":"none"},"agentmail_create_draft":{"id":"agentmail_create_draft","name":"Create Draft","description":"Create a new email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to create the draft in"},"to":{"type":"string","required":false,"visibility":"user-or-llm","description":"Recipient email addresses (comma-separated)"},"subject":{"type":"string","required":false,"visibility":"user-or-llm","description":"Draft subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text draft body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML draft body"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"},"inReplyTo":{"type":"string","required":false,"visibility":"user-or-llm","description":"ID of message being replied to"},"sendAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"ISO 8601 timestamp to schedule sending"}},"hostedApiKey":"none"},"agentmail_create_inbox":{"id":"agentmail_create_inbox","name":"Create Inbox","description":"Create a new email inbox with AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"username":{"type":"string","required":false,"visibility":"user-or-llm","description":"Username for the inbox email address"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Domain for the inbox email address"},"displayName":{"type":"string","required":false,"visibility":"user-or-llm","description":"Display name for the inbox"}},"hostedApiKey":"none"},"agentmail_delete_draft":{"id":"agentmail_delete_draft","name":"Delete Draft","description":"Delete an email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the draft"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to delete"}},"hostedApiKey":"none"},"agentmail_delete_inbox":{"id":"agentmail_delete_inbox","name":"Delete Inbox","description":"Delete an email inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to delete"}},"hostedApiKey":"none"},"agentmail_delete_thread":{"id":"agentmail_delete_thread","name":"Delete Thread","description":"Delete an email thread in AgentMail (moves to trash, or permanently deletes if already in trash)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the thread"},"threadId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the thread to delete"},"permanent":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Force permanent deletion instead of moving to trash"}},"hostedApiKey":"none"},"agentmail_forward_message":{"id":"agentmail_forward_message","name":"Forward Message","description":"Forward an email message to new recipients in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the message"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to forward"},"to":{"type":"string","required":true,"visibility":"user-or-llm","description":"Recipient email addresses (comma-separated)"},"subject":{"type":"string","required":false,"visibility":"user-or-llm","description":"Override subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Additional plain text to prepend"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"Additional HTML to prepend"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"}},"hostedApiKey":"none"},"agentmail_get_draft":{"id":"agentmail_get_draft","name":"Get Draft","description":"Get details of a specific email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox the draft belongs to"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to retrieve"}},"hostedApiKey":"none"},"agentmail_get_inbox":{"id":"agentmail_get_inbox","name":"Get Inbox","description":"Get details of a specific email inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to retrieve"}},"hostedApiKey":"none"},"agentmail_get_message":{"id":"agentmail_get_message","name":"Get Message","description":"Get details of a specific email message in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the message"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to retrieve"}},"hostedApiKey":"none"},"agentmail_get_thread":{"id":"agentmail_get_thread","name":"Get Thread","description":"Get details of a specific email thread including messages in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the thread"},"threadId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the thread to retrieve"}},"hostedApiKey":"none"},"agentmail_list_drafts":{"id":"agentmail_list_drafts","name":"List Drafts","description":"List email drafts in an inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to list drafts from"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of drafts to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"}},"hostedApiKey":"none"},"agentmail_list_inboxes":{"id":"agentmail_list_inboxes","name":"List Inboxes","description":"List all email inboxes in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of inboxes to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"}},"hostedApiKey":"none"},"agentmail_list_messages":{"id":"agentmail_list_messages","name":"List Messages","description":"List messages in an inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to list messages from"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of messages to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"}},"hostedApiKey":"none"},"agentmail_list_threads":{"id":"agentmail_list_threads","name":"List Threads","description":"List email threads in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to list threads from"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of threads to return"},"pageToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token for next page of results"},"labels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to filter threads by"},"before":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter threads before this ISO 8601 timestamp"},"after":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter threads after this ISO 8601 timestamp"}},"hostedApiKey":"none"},"agentmail_reply_message":{"id":"agentmail_reply_message","name":"Reply to Message","description":"Reply to an existing email message in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to reply from"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to reply to"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text reply body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML reply body"},"to":{"type":"string","required":false,"visibility":"user-or-llm","description":"Override recipient email addresses (comma-separated)"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC email addresses (comma-separated)"},"replyAll":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Reply to all recipients of the original message"}},"hostedApiKey":"none"},"agentmail_send_draft":{"id":"agentmail_send_draft","name":"Send Draft","description":"Send an existing email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the draft"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to send"}},"hostedApiKey":"none"},"agentmail_send_message":{"id":"agentmail_send_message","name":"Send Message","description":"Send an email message from an AgentMail inbox","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to send from"},"to":{"type":"string","required":true,"visibility":"user-or-llm","description":"Recipient email address (comma-separated for multiple)"},"subject":{"type":"string","required":true,"visibility":"user-or-llm","description":"Email subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text email body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML email body"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"}},"hostedApiKey":"none"},"agentmail_update_draft":{"id":"agentmail_update_draft","name":"Update Draft","description":"Update an existing email draft in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the draft"},"draftId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the draft to update"},"to":{"type":"string","required":false,"visibility":"user-or-llm","description":"Recipient email addresses (comma-separated)"},"subject":{"type":"string","required":false,"visibility":"user-or-llm","description":"Draft subject line"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Plain text draft body"},"html":{"type":"string","required":false,"visibility":"user-or-llm","description":"HTML draft body"},"cc":{"type":"string","required":false,"visibility":"user-or-llm","description":"CC recipient email addresses (comma-separated)"},"bcc":{"type":"string","required":false,"visibility":"user-or-llm","description":"BCC recipient email addresses (comma-separated)"},"sendAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"ISO 8601 timestamp to schedule sending"}},"hostedApiKey":"none"},"agentmail_update_inbox":{"id":"agentmail_update_inbox","name":"Update Inbox","description":"Update the display name of an email inbox in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox to update"},"displayName":{"type":"string","required":true,"visibility":"user-or-llm","description":"New display name for the inbox"}},"hostedApiKey":"none"},"agentmail_update_message":{"id":"agentmail_update_message","name":"Update Message","description":"Add or remove labels on an email message in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the message"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to update"},"addLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to add to the message"},"removeLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to remove from the message"}},"hostedApiKey":"none"},"agentmail_update_thread":{"id":"agentmail_update_thread","name":"Update Thread Labels","description":"Add or remove labels on an email thread in AgentMail","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentMail API key"},"inboxId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the inbox containing the thread"},"threadId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the thread to update"},"addLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to add to the thread"},"removeLabels":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated labels to remove from the thread"}},"hostedApiKey":"none"},"agentphone_create_call":{"id":"agentphone_create_call","name":"Create Outbound Call","description":"Initiate an outbound voice call from an AgentPhone agent","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"agentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Agent that will handle the call"},"toNumber":{"type":"string","required":true,"visibility":"user-or-llm","description":"Phone number to call in E.164 format (e.g. +14155551234)"},"fromNumberId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Phone number ID to use as caller ID. Must belong to the agent. If omitted, the agent\'s first assigned number is used."},"initialGreeting":{"type":"string","required":false,"visibility":"user-or-llm","description":"Optional greeting spoken when the recipient answers"},"voice":{"type":"string","required":false,"visibility":"user-or-llm","description":"Voice ID override for this call (defaults to the agent\'s configured voice)"},"systemPrompt":{"type":"string","required":false,"visibility":"user-or-llm","description":"When provided, uses a built-in LLM for the conversation instead of forwarding to your webhook"}},"hostedApiKey":"none"},"agentphone_create_contact":{"id":"agentphone_create_contact","name":"Create Contact","description":"Create a new contact in AgentPhone","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"phoneNumber":{"type":"string","required":true,"visibility":"user-or-llm","description":"Phone number in E.164 format (e.g. +14155551234)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact\'s full name"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Contact\'s email address"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Freeform notes stored on the contact"}},"hostedApiKey":"none"},"agentphone_create_number":{"id":"agentphone_create_number","name":"Create Phone Number","description":"Provision a new SMS- and voice-enabled phone number","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Two-letter country code (e.g. US, CA). Defaults to US."},"areaCode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Preferred area code (US/CA only, e.g. \\"415\\"). Best-effort — may be ignored if unavailable."},"agentId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Optionally attach the number to an agent immediately"}},"hostedApiKey":"none"},"agentphone_delete_contact":{"id":"agentphone_delete_contact","name":"Delete Contact","description":"Delete a contact by ID","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"contactId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact ID"}},"hostedApiKey":"none"},"agentphone_get_call":{"id":"agentphone_get_call","name":"Get Call","description":"Fetch a call and its full transcript","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"callId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the call to retrieve"}},"hostedApiKey":"none"},"agentphone_get_call_transcript":{"id":"agentphone_get_call_transcript","name":"Get Call Transcript","description":"Get the full ordered transcript for a call","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"callId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the call to retrieve the transcript for"}},"hostedApiKey":"none"},"agentphone_get_contact":{"id":"agentphone_get_contact","name":"Get Contact","description":"Fetch a single contact by ID","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"contactId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact ID"}},"hostedApiKey":"none"},"agentphone_get_conversation":{"id":"agentphone_get_conversation","name":"Get Conversation","description":"Get a conversation along with its recent messages","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"conversationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Conversation ID"},"messageLimit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of recent messages to include (default 50, max 100)"}},"hostedApiKey":"none"},"agentphone_get_conversation_messages":{"id":"agentphone_get_conversation_messages","name":"Get Conversation Messages","description":"Get paginated messages for a conversation","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"conversationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Conversation ID"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of messages to return (default 50, max 200)"},"before":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received before this ISO 8601 timestamp"},"after":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received after this ISO 8601 timestamp"}},"hostedApiKey":"none"},"agentphone_get_number_messages":{"id":"agentphone_get_number_messages","name":"Get Phone Number Messages","description":"Fetch messages received on a specific phone number","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"numberId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the phone number"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of messages to return (default 50, max 200)"},"before":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received before this ISO 8601 timestamp"},"after":{"type":"string","required":false,"visibility":"user-or-llm","description":"Return messages received after this ISO 8601 timestamp"}},"hostedApiKey":"none"},"agentphone_get_usage":{"id":"agentphone_get_usage","name":"Get Usage","description":"Retrieve current usage statistics for the AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"}},"hostedApiKey":"none"},"agentphone_get_usage_daily":{"id":"agentphone_get_usage_daily","name":"Get Daily Usage","description":"Get a daily breakdown of usage (messages, calls, webhooks) for the last N days","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"days":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of days to return (1-365, default 30)"}},"hostedApiKey":"none"},"agentphone_get_usage_monthly":{"id":"agentphone_get_usage_monthly","name":"Get Monthly Usage","description":"Get monthly usage aggregation (messages, calls, webhooks) for the last N months","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"months":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of months to return (1-24, default 6)"}},"hostedApiKey":"none"},"agentphone_list_calls":{"id":"agentphone_list_calls","name":"List Calls","description":"List voice calls for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 20, max 100)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"},"status":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by status (completed, in-progress, failed)"},"direction":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by direction (inbound, outbound)"},"type":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by call type (pstn, web)"},"search":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search by phone number (matches fromNumber or toNumber)"}},"hostedApiKey":"none"},"agentphone_list_contacts":{"id":"agentphone_list_contacts","name":"List Contacts","description":"List contacts for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"search":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter by name or phone number (case-insensitive contains)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 50, max 200)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"}},"hostedApiKey":"none"},"agentphone_list_conversations":{"id":"agentphone_list_conversations","name":"List Conversations","description":"List conversations (message threads) for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 20, max 100)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"}},"hostedApiKey":"none"},"agentphone_list_numbers":{"id":"agentphone_list_numbers","name":"List Phone Numbers","description":"List all phone numbers provisioned for this AgentPhone account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to return (default 20, max 100)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip (min 0)"}},"hostedApiKey":"none"},"agentphone_react_to_message":{"id":"agentphone_react_to_message","name":"React to Message","description":"Send an iMessage tapback reaction to a message (iMessage only)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"messageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the message to react to"},"reaction":{"type":"string","required":true,"visibility":"user-or-llm","description":"Reaction type: love, like, dislike, laugh, emphasize, or question"}},"hostedApiKey":"none"},"agentphone_release_number":{"id":"agentphone_release_number","name":"Release Phone Number","description":"Release (delete) a phone number. This action is irreversible.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"numberId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the phone number to release"}},"hostedApiKey":"none"},"agentphone_send_message":{"id":"agentphone_send_message","name":"Send Message","description":"Send an outbound SMS or iMessage from an AgentPhone agent","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"agentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Agent sending the message"},"toNumber":{"type":"string","required":true,"visibility":"user-or-llm","description":"Recipient phone number in E.164 format (e.g. +14155551234)"},"body":{"type":"string","required":true,"visibility":"user-or-llm","description":"Message text to send"},"mediaUrl":{"type":"string","required":false,"visibility":"user-or-llm","description":"Optional URL of an image, video, or file to attach"},"numberId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Phone number ID to send from. If omitted, the agent\'s first assigned number is used."}},"hostedApiKey":"none"},"agentphone_update_contact":{"id":"agentphone_update_contact","name":"Update Contact","description":"Update a contact\'s fields","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"contactId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Contact ID"},"phoneNumber":{"type":"string","required":false,"visibility":"user-or-llm","description":"New phone number in E.164 format"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New contact name"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"New email address"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"New freeform notes"}},"hostedApiKey":"none"},"agentphone_update_conversation":{"id":"agentphone_update_conversation","name":"Update Conversation","description":"Update conversation metadata (stored state). Pass null to clear existing metadata.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"AgentPhone API key"},"conversationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Conversation ID"},"metadata":{"type":"json","required":false,"visibility":"user-or-llm","description":"Custom key-value metadata to store on the conversation. Pass null to clear existing metadata."}},"hostedApiKey":"none"},"agiloft_async_status":{"id":"agiloft_async_status","name":"Agiloft Async Status","description":"Check whether an asynchronous Agiloft call, such as a run action button, has completed.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table the asynchronous call was made against"},"callbackId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Callback ID returned by the asynchronous call, e.g. from Run Action Button"}},"hostedApiKey":"none"},"agiloft_attach_file":{"id":"agiloft_attach_file","name":"Agiloft Attach File","description":"Attach a file to a field in an Agiloft record.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to attach the file to"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field"},"file":{"type":"file","required":true,"visibility":"user-or-llm","description":"File to attach"},"fileName":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name to assign to the file (defaults to original file name)"},"overwrite":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Replace the contents of the field instead of adding another file to it"}},"hostedApiKey":"none"},"agiloft_attachment_info":{"id":"agiloft_attachment_info","name":"Agiloft Attachment Info","description":"Get information about file attachments on a record field.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to check attachments on"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field to inspect"}},"hostedApiKey":"none"},"agiloft_create_record":{"id":"agiloft_create_record","name":"Agiloft Create Record","description":"Create a new record in an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"data":{"type":"string","required":true,"visibility":"user-or-llm","description":"Record field values as a JSON object (e.g., {\\"first_name\\": \\"John\\", \\"status\\": \\"Active\\"})"}},"hostedApiKey":"none"},"agiloft_delete_record":{"id":"agiloft_delete_record","name":"Agiloft Delete Record","description":"Delete a record from an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to delete"},"substituteIds":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated IDs of records that adopt the dependants of the deleted record. Read only when the delete rule is REPLACE_WITH_ANOTHER."},"deleteRule":{"type":"string","required":false,"visibility":"user-or-llm","description":"How to treat records that depend on this one: ERROR_IF_DEPENDANTS (default — fails rather than cascading), APPLY_DELETE_WHERE_POSSIBLE, DELETE_WHERE_POSSIBLE_OTHERWISE_UNLINK, APPLY_UNLINK, UNLINK_WHERE_POSSIBLE_OTHERWISE_DELETE, or REPLACE_WITH_ANOTHER"}},"hostedApiKey":"none"},"agiloft_get_choice_line_id":{"id":"agiloft_get_choice_line_id","name":"Agiloft Get Choice Line ID","description":"Resolve the internal numeric ID of a choice-list value, for use in EWSelect WHERE clauses against choice fields.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"case\\", \\"contracts\\")"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Choice field name (e.g., \\"priority\\", \\"status\\")"},"value":{"type":"string","required":true,"visibility":"user-or-llm","description":"Choice display value to resolve (e.g., \\"High\\", \\"Active\\")"}},"hostedApiKey":"none"},"agiloft_list_tables":{"id":"agiloft_list_tables","name":"Agiloft List Tables","description":"List the tables and fields in an Agiloft knowledge base, to discover the logical names other operations need.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":false,"visibility":"user-or-llm","description":"Logical name of a single table to describe (e.g., \\"contacts\\"). Leave empty to list every table in the knowledge base."},"includeLinkedInfo":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Include the source table and column behind each linked field"},"skipColumnsInfo":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Return table names only, omitting field details, for a much smaller response"}},"hostedApiKey":"none"},"agiloft_lock_record":{"id":"agiloft_lock_record","name":"Agiloft Lock Record","description":"Lock, unlock, or check the lock status of an Agiloft record.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to lock, unlock, or check"},"lockAction":{"type":"string","required":true,"visibility":"user-or-llm","description":"Action to perform: \\"lock\\", \\"unlock\\", or \\"check\\""},"force":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Unlock only: release a lock held by another user."}},"hostedApiKey":"none"},"agiloft_nlp_search":{"id":"agiloft_nlp_search","name":"Agiloft Natural Language Search","description":"Search Agiloft records by describing what you want in plain language, such as \\"active NDAs submitted last month\\".","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"nlpQuery":{"type":"string","required":true,"visibility":"user-or-llm","description":"The request in plain language, e.g. \\"Show me open, high-priority contracts\\". Structured field filters are not accepted — use Search Records for those."},"fields":{"type":"string","required":true,"visibility":"user-or-llm","description":"Comma-separated field names to return, e.g. \\"id, contract_title1, company_name\\""},"page":{"type":"string","required":false,"visibility":"user-or-llm","description":"Page number, starting from 0"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Records per page"}},"hostedApiKey":"none"},"agiloft_read_record":{"id":"agiloft_read_record","name":"Agiloft Read Record","description":"Read a record by ID from an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to read"},"fields":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of field names to include in the response"}},"hostedApiKey":"none"},"agiloft_remove_attachment":{"id":"agiloft_remove_attachment","name":"Agiloft Remove Attachment","description":"Remove an attached file from a field in an Agiloft record.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record containing the attachment"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field"},"position":{"type":"string","required":true,"visibility":"user-or-llm","description":"Position index of the file to remove (starting from 0)"}},"hostedApiKey":"none"},"agiloft_retrieve_attachment":{"id":"agiloft_retrieve_attachment","name":"Agiloft Retrieve Attachment","description":"Download an attached file from an Agiloft record field.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record containing the attachment"},"fieldName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the attachment field"},"position":{"type":"string","required":true,"visibility":"user-or-llm","description":"Position index of the file in the field (starting from 0)"}},"hostedApiKey":"none"},"agiloft_run_action_button":{"id":"agiloft_run_action_button","name":"Agiloft Run Action Button","description":"Run an action button on an Agiloft record, such as an approval or send-for-signature step.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"case\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to run the action button on"},"actionButtonField":{"type":"string","required":true,"visibility":"user-or-llm","description":"Logical name of the field holding the action button (e.g., \\"ab_field\\")"}},"hostedApiKey":"none"},"agiloft_saved_search":{"id":"agiloft_saved_search","name":"Agiloft Saved Search","description":"List the saved searches defined for an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Logical table name to list saved searches for (e.g., \\"contract\\")"}},"hostedApiKey":"none"},"agiloft_search_records":{"id":"agiloft_search_records","name":"Agiloft Search Records","description":"Search for records in an Agiloft table using a query.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name to search in (e.g., \\"contracts\\", \\"contacts.employees\\")"},"query":{"type":"string","required":false,"visibility":"user-or-llm","description":"Ad hoc EWSearch query. Combine conditions with && (and) or || (or) and quote every value — e.g. \\"summary~=\'test\'&&priority=\'High\'\\". Required unless a saved search is given."},"search":{"type":"string","required":false,"visibility":"user-or-llm","description":"Label of a saved search defined on the table (e.g., \\"C: Status is Closed\\"). Can be combined with a query to narrow it further."},"fields":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of field names to include in the results"},"page":{"type":"string","required":false,"visibility":"user-or-llm","description":"Page number for paginated results (starting from 0)"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of records to return per page. Agiloft treats 0 as \\"all records\\", so leave it unset or use a positive value to keep result sizes bounded."}},"hostedApiKey":"none"},"agiloft_select_records":{"id":"agiloft_select_records","name":"Agiloft Select Records","description":"Select record IDs matching a SQL WHERE clause from an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"where":{"type":"string","required":true,"visibility":"user-or-llm","description":"SQL WHERE clause using database column names (e.g., \\"summary like \'%new%\'\\" or \\"assigned_person=\'John Doe\'\\"). EWSelect has no page size and returns every matching ID, so append a database limit such as \\"limit 0,200\\" to bound the result."}},"hostedApiKey":"none"},"agiloft_update_record":{"id":"agiloft_update_record","name":"Agiloft Update Record","description":"Update an existing record in an Agiloft table.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the record to update"},"data":{"type":"string","required":true,"visibility":"user-or-llm","description":"Updated field values as a JSON object (e.g., {\\"status\\": \\"Active\\", \\"priority\\": \\"High\\"})"}},"hostedApiKey":"none"},"agiloft_upsert_record":{"id":"agiloft_upsert_record","name":"Agiloft Upsert Record","description":"Create an Agiloft record, or update it when a record already matches the given fields.","version":"1.0.0","params":{"instanceUrl":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft instance URL (e.g., https://mycompany.agiloft.com)"},"knowledgeBase":{"type":"string","required":true,"visibility":"user-only","description":"Knowledge base name"},"login":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft username"},"password":{"type":"string","required":true,"visibility":"user-only","description":"Agiloft password"},"table":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table name (e.g., \\"contracts\\", \\"contacts.employees\\")"},"match":{"type":"string","required":true,"visibility":"user-or-llm","description":"Field used to find an existing record (e.g., \\"ext_id\\"). Pick something that identifies a record uniquely — if more than one record matches, Agiloft writes nothing and returns a conflict."},"async":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Queue the write instead of waiting for it. Returns a callback ID instead of a record ID; pass that to Async Status to poll the result."},"data":{"type":"string","required":true,"visibility":"user-or-llm","description":"Field values as a JSON object. On create these populate the new record; on update only the supplied fields change."}},"hostedApiKey":"none"},"ahrefs_anchors":{"id":"ahrefs_anchors","name":"Ahrefs Anchors","description":"Get the anchor text distribution for a target domain or URL\'s backlinks, showing how many links and referring domains use each anchor text.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"history":{"type":"string","required":false,"visibility":"user-or-llm","description":"Historical scope: \\"live\\" (currently live), \\"all_time\\" (default, includes lost backlinks), or \\"since:YYYY-MM-DD\\" (backlinks found since a date)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_backlinks":{"id":"ahrefs_backlinks","name":"Ahrefs Backlinks","description":"Get a list of backlinks pointing to a target domain or URL. Returns details about each backlink including source URL, anchor text, and domain rating.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"history":{"type":"string","required":false,"visibility":"user-or-llm","description":"Historical scope: \\"live\\" (currently live backlinks), \\"all_time\\" (default, includes lost backlinks), or \\"since:YYYY-MM-DD\\" (backlinks found since a date)."},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_backlinks_stats":{"id":"ahrefs_backlinks_stats","name":"Ahrefs Backlinks Stats","description":"Get backlink and referring domain totals for a target domain or URL, both currently live and across all time.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_batch_analysis":{"id":"ahrefs_batch_analysis","name":"Ahrefs Batch Analysis","description":"Get bulk SEO metrics (Domain Rating, backlinks, referring domains, organic traffic, and more) for multiple domains or URLs in a single request. Useful for comparing many competitors at once.","version":"1.0.0","params":{"targets":{"type":"string","required":true,"visibility":"user-or-llm","description":"Comma-separated list of domains or URLs to analyze. Example: \\"example.com,competitor.com\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode applied to every target: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"protocol":{"type":"string","required":false,"visibility":"user-or-llm","description":"Protocol applied to every target: \\"both\\" (default), \\"http\\", or \\"https\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_broken_backlinks":{"id":"ahrefs_broken_backlinks","name":"Ahrefs Broken Backlinks","description":"Get a list of broken backlinks pointing to a target domain or URL. Useful for identifying link reclamation opportunities.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_domain_rating":{"id":"ahrefs_domain_rating","name":"Ahrefs Domain Rating","description":"Get the Domain Rating (DR) and Ahrefs Rank for a target domain. Domain Rating shows the strength of a website\'s backlink profile on a scale from 0 to 100.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain to analyze (e.g., example.com)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date for historical data in YYYY-MM-DD format (defaults to today)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_domain_rating_history":{"id":"ahrefs_domain_rating_history","name":"Ahrefs Domain Rating History","description":"Get the historical Domain Rating (DR) trend for a target domain or URL over a date range, grouped daily, weekly, or monthly.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_keyword_overview":{"id":"ahrefs_keyword_overview","name":"Ahrefs Keyword Overview","description":"Get detailed metrics for a keyword including search volume, keyword difficulty, CPC, clicks, and traffic potential.","version":"1.0.0","params":{"keyword":{"type":"string","required":true,"visibility":"user-or-llm","description":"The keyword to analyze"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for keyword data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_keywords_history":{"id":"ahrefs_keywords_history","name":"Ahrefs Keywords History","description":"Get the historical organic keyword ranking distribution for a target domain or URL over a date range: how many keywords rank in each position bucket at each point in time.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for search results. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_metrics":{"id":"ahrefs_metrics","name":"Ahrefs Metrics","description":"Get a one-call organic and paid search overview for a target domain or URL: organic traffic, organic keywords, paid traffic, paid keywords, and estimated traffic cost.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_metrics_history":{"id":"ahrefs_metrics_history","name":"Ahrefs Metrics History","description":"Get the historical organic and paid traffic trend for a target domain or URL over a date range: organic traffic/cost and paid traffic/cost at each point in time.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_organic_competitors":{"id":"ahrefs_organic_competitors","name":"Ahrefs Organic Competitors","description":"Get domains that compete with a target domain or URL for the same organic keywords, ranked by keyword overlap.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for search results. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_organic_keywords":{"id":"ahrefs_organic_keywords","name":"Ahrefs Organic Keywords","description":"Get organic keywords that a target domain or URL ranks for in Google search results. Returns keyword details including search volume, ranking position, and estimated traffic.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for search results. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_paid_pages":{"id":"ahrefs_paid_pages","name":"Ahrefs Paid Pages","description":"Get a target domain\'s pages that receive paid search traffic, sorted by estimated paid traffic. Returns page URLs with their paid traffic, keyword counts, and estimated spend.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_rank_tracker_competitors_overview":{"id":"ahrefs_rank_tracker_competitors_overview","name":"Ahrefs Rank Tracker Competitors Overview","description":"Get competitor rankings for the keywords tracked in an Ahrefs Rank Tracker project: each tracked keyword\'s volume and difficulty alongside every competitor\'s position, traffic, and traffic value. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":true,"visibility":"user-only","description":"Date to report rankings for, in YYYY-MM-DD format"},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"dateCompared":{"type":"string","required":false,"visibility":"user-only","description":"Comparison date in YYYY-MM-DD format, to compute position/traffic deltas"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_rank_tracker_competitors_stats":{"id":"ahrefs_rank_tracker_competitors_stats","name":"Ahrefs Rank Tracker Competitors Stats","description":"Get aggregate competitor stats for an Ahrefs Rank Tracker project: each competitor\'s traffic, traffic value, average position, and share of voice across all tracked keywords. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":true,"visibility":"user-only","description":"Date to report metrics for, in YYYY-MM-DD format"},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_rank_tracker_overview":{"id":"ahrefs_rank_tracker_overview","name":"Ahrefs Rank Tracker Overview","description":"Get ranking overview metrics for the keywords tracked in an Ahrefs Rank Tracker project: position, search volume, keyword difficulty, and estimated traffic. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":true,"visibility":"user-only","description":"Date to report rankings for, in YYYY-MM-DD format"},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"dateCompared":{"type":"string","required":false,"visibility":"user-only","description":"Comparison date in YYYY-MM-DD format, to compute position/traffic deltas"},"volumeMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search volume calculation: \\"monthly\\" or \\"average\\" (default: \\"monthly\\")"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_rank_tracker_serp_overview":{"id":"ahrefs_rank_tracker_serp_overview","name":"Ahrefs Rank Tracker SERP Overview","description":"Get the full SERP (search engine results page) for a keyword tracked in an Ahrefs Rank Tracker project, including every ranking URL with its position, title, and authority metrics. This endpoint is free and does not consume API units.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Rank Tracker project ID (found in the project URL in Ahrefs)"},"keyword":{"type":"string","required":true,"visibility":"user-or-llm","description":"The tracked keyword to retrieve SERP data for"},"country":{"type":"string","required":true,"visibility":"user-or-llm","description":"Country code for the tracked keyword. Example: \\"us\\", \\"gb\\", \\"de\\""},"device":{"type":"string","required":true,"visibility":"user-or-llm","description":"Rankings device type: \\"desktop\\" or \\"mobile\\""},"topPositions":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of top organic positions to return (defaults to all available)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Timestamp to return the last available SERP Overview at, in YYYY-MM-DDThh:mm:ss format"},"locationId":{"type":"number","required":false,"visibility":"user-or-llm","description":"Location ID of the tracked keyword, if tracked at a specific location"},"languageCode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Language code of the tracked keyword"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_refdomains_history":{"id":"ahrefs_refdomains_history","name":"Ahrefs Referring Domains History","description":"Get the historical referring domains trend for a target domain or URL over a date range, grouped daily, weekly, or monthly.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\""},"dateFrom":{"type":"string","required":true,"visibility":"user-only","description":"Start date of the historical period, in YYYY-MM-DD format"},"dateTo":{"type":"string","required":false,"visibility":"user-only","description":"End date of the historical period, in YYYY-MM-DD format (defaults to today)"},"historyGrouping":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval for grouping data points: \\"daily\\", \\"weekly\\", or \\"monthly\\" (default: \\"monthly\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_referring_domains":{"id":"ahrefs_referring_domains","name":"Ahrefs Referring Domains","description":"Get a list of domains that link to a target domain or URL. Returns unique referring domains with their domain rating, backlink counts, and discovery dates.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain or URL to analyze. Example: \\"example.com\\" or \\"https://example.com/page\\""},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"history":{"type":"string","required":false,"visibility":"user-or-llm","description":"Historical scope: \\"live\\" (currently live), \\"all_time\\" (default, includes lost domains), or \\"since:YYYY-MM-DD\\" (domains found since a date)."},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_related_terms":{"id":"ahrefs_related_terms","name":"Ahrefs Related Terms","description":"Get keyword ideas related to a seed keyword: terms the same top-ranking pages also rank for (\\"also rank for\\") or also discuss (\\"also talk about\\"), with volume, difficulty, and CPC.","version":"1.0.0","params":{"keyword":{"type":"string","required":true,"visibility":"user-or-llm","description":"The seed keyword to find related terms for"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for keyword data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"terms":{"type":"string","required":false,"visibility":"user-or-llm","description":"Type of related keywords to return: \\"also_rank_for\\", \\"also_talk_about\\", or \\"all\\" (default: \\"all\\")"},"viewFor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Whether to derive related terms from the top 10 or top 100 ranking pages (default: \\"top_10\\")"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_site_audit_page_explorer":{"id":"ahrefs_site_audit_page_explorer","name":"Ahrefs Site Audit Page Explorer","description":"Get crawled pages from an Ahrefs Site Audit project with health and SEO metrics: HTTP status, title, link counts, backlinks, indexability, and traffic. Optionally filter to pages affected by a specific issue.","version":"1.0.0","params":{"projectId":{"type":"number","required":true,"visibility":"user-or-llm","description":"The Site Audit project ID (found in the project URL in Ahrefs)"},"date":{"type":"string","required":false,"visibility":"user-only","description":"Crawl date in YYYY-MM-DDThh:mm:ss format (defaults to the most recent crawl)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of results to skip, for pagination"},"issueId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Only return pages affected by this issue ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"ahrefs_top_pages":{"id":"ahrefs_top_pages","name":"Ahrefs Top Pages","description":"Get the top pages of a target domain sorted by organic traffic. Returns page URLs with their traffic, keyword counts, and estimated traffic value.","version":"1.0.0","params":{"target":{"type":"string","required":true,"visibility":"user-or-llm","description":"The target domain to analyze. Example: \\"example.com\\""},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Country code for traffic data. Example: \\"us\\", \\"gb\\", \\"de\\" (default: \\"us\\")"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: \\"domain\\""},"date":{"type":"string","required":false,"visibility":"user-only","description":"Date to report metrics on, in YYYY-MM-DD format (defaults to today)"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of results to return. Example: 50 (default: 1000)"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ahrefs API Key"}},"hostedApiKey":"none"},"airtable_create_records":{"id":"airtable_create_records","name":"Airtable Create Records","description":"Write new records to an Airtable table","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"records":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of records to create, each with a `fields` object"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_delete_records":{"id":"airtable_delete_records","name":"Airtable Delete Records","description":"Delete one or more records from an Airtable table by ID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"recordIds":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of record IDs to delete (each starts with \\"rec\\", e.g., [\\"recXXXXXXXXXXXXXX\\"]). Pass a single-element array to delete one record."}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_get_base_schema":{"id":"airtable_get_base_schema","name":"Airtable Get Base Schema","description":"Get the schema of all tables, fields, and views in an Airtable base","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_get_record":{"id":"airtable_get_record","name":"Airtable Get Record","description":"Retrieve a single record from an Airtable table by its ID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Record ID to retrieve (starts with \\"rec\\", e.g., \\"recXXXXXXXXXXXXXX\\")"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_list_bases":{"id":"airtable_list_bases","name":"Airtable List Bases","description":"List all bases the authenticated user has access to","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"offset":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination offset for retrieving additional bases"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_list_records":{"id":"airtable_list_records","name":"Airtable List Records","description":"Read records from an Airtable table","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"maxRecords":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of records to return (default: all records)"},"filterFormula":{"type":"string","required":false,"visibility":"user-or-llm","description":"Formula to filter records (e.g., \\"({Field Name} = \'Value\')\\")"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_list_tables":{"id":"airtable_list_tables","name":"Airtable List Tables","description":"List all tables and their schema in an Airtable base","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_update_multiple_records":{"id":"airtable_update_multiple_records","name":"Airtable Update Multiple Records","description":"Update multiple existing records in an Airtable table","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"records":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of records to update, each with an `id` and a `fields` object"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_update_record":{"id":"airtable_update_record","name":"Airtable Update Record","description":"Update an existing record in an Airtable table by ID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"recordId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Record ID to update (starts with \\"rec\\", e.g., \\"recXXXXXXXXXXXXXX\\")"},"fields":{"type":"json","required":true,"visibility":"user-or-llm","description":"An object containing the field names and their new values"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airtable_upsert_records":{"id":"airtable_upsert_records","name":"Airtable Upsert Records","description":"Update existing records or create new ones in an Airtable table, matching on the specified merge fields","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token"},"baseId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Airtable base ID (starts with \\"app\\", e.g., \\"appXXXXXXXXXXXXXX\\")"},"tableId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Table ID (starts with \\"tbl\\") or table name"},"records":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of records to upsert, each with a `fields` object"},"fieldsToMergeOn":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of field names used to match existing records (max 3). A record is updated when all merge fields match, otherwise it is created. Example: [\\"Name\\"]"},"typecast":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, Airtable automatically converts string values to the field type"}},"oauth":{"required":true,"provider":"airtable"},"hostedApiKey":"none"},"airweave_search":{"id":"airweave_search","name":"Airweave Search","description":"Search your synced data collections using Airweave. Supports semantic search with hybrid, neural, or keyword retrieval strategies. Optionally generate AI-powered answers from search results.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Airweave API Key for authentication"},"collectionId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The readable ID of the collection to search"},"query":{"type":"string","required":true,"visibility":"user-or-llm","description":"The search query text"},"limit":{"type":"number","required":false,"visibility":"user-only","description":"Maximum number of results to return (default: 100)"},"retrievalStrategy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Retrieval strategy: hybrid (default), neural, or keyword"},"expandQuery":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Generate query variations to improve recall"},"rerank":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Reorder results for improved relevance using LLM"},"generateAnswer":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Generate a natural-language answer to the query"}},"hostedApiKey":"none"},"algolia_add_record":{"id":"algolia_add_record","name":"Algolia Add Record","description":"Add or replace a record in an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":false,"visibility":"user-or-llm","description":"Object ID for the record (auto-generated if not provided)"},"record":{"type":"json","required":true,"visibility":"user-or-llm","description":"JSON object representing the record to add"}},"hostedApiKey":"none"},"algolia_batch_operations":{"id":"algolia_batch_operations","name":"Algolia Batch Operations","description":"Perform batch add, update, partial update, or delete operations on records in an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"requests":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of batch operations. Each item has \\"action\\" (addObject, updateObject, partialUpdateObject, partialUpdateObjectNoCreate, deleteObject, delete, clear) and \\"body\\" (the record data; must include objectID for update/delete; use an empty object {} for the index-level delete/clear actions)"}},"hostedApiKey":"none"},"algolia_browse_records":{"id":"algolia_browse_records","name":"Algolia Browse Records","description":"Browse and iterate over all records in an Algolia index using cursor pagination","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key (must have browse ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to browse"},"query":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search query to filter browsed records"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter string to narrow down results"},"attributesToRetrieve":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of attributes to retrieve"},"hitsPerPage":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of hits per page (default: 1000, max: 1000)"},"cursor":{"type":"string","required":false,"visibility":"user-or-llm","description":"Cursor from a previous browse response for pagination"},"aroundLatLng":{"type":"string","required":false,"visibility":"user-or-llm","description":"Coordinates for geo-search (e.g., \\"40.71,-74.01\\")"},"aroundRadius":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum radius in meters for geo-search, or \\"all\\" for unlimited"},"insideBoundingBox":{"type":"json","required":false,"visibility":"user-or-llm","description":"Bounding box coordinates as [[lat1, lng1, lat2, lng2]] for geo-search"},"insidePolygon":{"type":"json","required":false,"visibility":"user-or-llm","description":"Polygon coordinates as [[lat1, lng1, lat2, lng2, lat3, lng3, ...]] for geo-search"}},"hostedApiKey":"none"},"algolia_clear_records":{"id":"algolia_clear_records","name":"Algolia Clear Records","description":"Clear all records from an Algolia index while keeping settings, synonyms, and rules","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have deleteIndex ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to clear"}},"hostedApiKey":"none"},"algolia_copy_move_index":{"id":"algolia_copy_move_index","name":"Algolia Copy/Move Index","description":"Copy or move an Algolia index to a new destination","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the source index"},"operation":{"type":"string","required":true,"visibility":"user-or-llm","description":"Operation to perform: \\"copy\\" or \\"move\\""},"destination":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the destination index"},"scope":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of scopes to copy (only for \\"copy\\" operation): [\\"settings\\", \\"synonyms\\", \\"rules\\"]. Omit to copy everything including records."}},"hostedApiKey":"none"},"algolia_delete_by_filter":{"id":"algolia_delete_by_filter","name":"Algolia Delete By Filter","description":"Delete all records matching a filter from an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have deleteIndex ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter expression to match records for deletion (e.g., \\"category:outdated\\")"},"facetFilters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of facet filters (e.g., [\\"brand:Acme\\"])"},"numericFilters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of numeric filters (e.g., [\\"price > 100\\"])"},"tagFilters":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of tag filters using the _tags attribute (e.g., [\\"published\\"])"},"aroundLatLng":{"type":"string","required":false,"visibility":"user-or-llm","description":"Coordinates for geo-search filter (e.g., \\"40.71,-74.01\\")"},"aroundRadius":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum radius in meters for geo-search, or \\"all\\" for unlimited"},"insideBoundingBox":{"type":"json","required":false,"visibility":"user-or-llm","description":"Bounding box coordinates as [[lat1, lng1, lat2, lng2]] for geo-search filter"},"insidePolygon":{"type":"json","required":false,"visibility":"user-or-llm","description":"Polygon coordinates as [[lat1, lng1, lat2, lng2, lat3, lng3, ...]] for geo-search filter"}},"hostedApiKey":"none"},"algolia_delete_index":{"id":"algolia_delete_index","name":"Algolia Delete Index","description":"Delete an entire Algolia index and all its records","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have deleteIndex ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to delete"}},"hostedApiKey":"none"},"algolia_delete_record":{"id":"algolia_delete_record","name":"Algolia Delete Record","description":"Delete a record by objectID from an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":true,"visibility":"user-or-llm","description":"The objectID of the record to delete"}},"hostedApiKey":"none"},"algolia_get_record":{"id":"algolia_get_record","name":"Algolia Get Record","description":"Get a record by objectID from an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":true,"visibility":"user-or-llm","description":"The objectID of the record to retrieve"},"attributesToRetrieve":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of attributes to retrieve"}},"hostedApiKey":"none"},"algolia_get_records":{"id":"algolia_get_records","name":"Algolia Get Records","description":"Retrieve multiple records by objectID from one or more Algolia indices","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Default index name for all requests"},"requests":{"type":"json","required":true,"visibility":"user-or-llm","description":"Array of objects specifying records to retrieve. Each must have \\"objectID\\" and optionally \\"indexName\\" and \\"attributesToRetrieve\\"."}},"hostedApiKey":"none"},"algolia_get_settings":{"id":"algolia_get_settings","name":"Algolia Get Settings","description":"Retrieve the settings of an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"}},"hostedApiKey":"none"},"algolia_get_task_status":{"id":"algolia_get_task_status","name":"Algolia Get Task Status","description":"Check whether an Algolia indexing task has finished publishing","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index the task ran against"},"taskID":{"type":"number","required":true,"visibility":"user-or-llm","description":"The taskID returned by a previous write operation"}},"hostedApiKey":"none"},"algolia_list_indices":{"id":"algolia_list_indices","name":"Algolia List Indices","description":"List all indices in an Algolia application","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for paginating indices (default: not paginated)"},"hitsPerPage":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of indices per page (default: 100)"}},"hostedApiKey":"none"},"algolia_partial_update_record":{"id":"algolia_partial_update_record","name":"Algolia Partial Update Record","description":"Partially update a record in an Algolia index without replacing it entirely","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"objectID":{"type":"string","required":true,"visibility":"user-or-llm","description":"The objectID of the record to update"},"attributes":{"type":"json","required":true,"visibility":"user-or-llm","description":"JSON object with attributes to update. Supports built-in operations like {\\"stock\\": {\\"_operation\\": \\"Decrement\\", \\"value\\": 1}}"},"createIfNotExists":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to create the record if it does not exist (default: true)"}},"hostedApiKey":"none"},"algolia_search":{"id":"algolia_search","name":"Algolia Search","description":"Search an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia API Key"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index to search"},"query":{"type":"string","required":true,"visibility":"user-or-llm","description":"Search query text"},"hitsPerPage":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of hits per page (default: 20)"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number to retrieve (default: 0)"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter string (e.g., \\"category:electronics AND price < 100\\")"},"attributesToRetrieve":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of attributes to retrieve"},"facets":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of facet attribute names to retrieve counts for (use \\"*\\" for all)"},"getRankingInfo":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to include detailed ranking information in each hit"},"aroundLatLng":{"type":"string","required":false,"visibility":"user-or-llm","description":"Coordinates for geo-search (e.g., \\"40.71,-74.01\\")"},"aroundRadius":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum radius in meters for geo-search, or \\"all\\" for unlimited"},"insideBoundingBox":{"type":"json","required":false,"visibility":"user-or-llm","description":"Bounding box coordinates as [[lat1, lng1, lat2, lng2]] for geo-search"},"insidePolygon":{"type":"json","required":false,"visibility":"user-or-llm","description":"Polygon coordinates as [[lat1, lng1, lat2, lng2, lat3, lng3, ...]] for geo-search"}},"hostedApiKey":"none"},"algolia_update_settings":{"id":"algolia_update_settings","name":"Algolia Update Settings","description":"Update the settings of an Algolia index","version":"1.0","params":{"applicationId":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Application ID"},"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Algolia Admin API Key (must have editSettings ACL)"},"indexName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the Algolia index"},"settings":{"type":"json","required":true,"visibility":"user-or-llm","description":"JSON object with settings to update (e.g., {\\"searchableAttributes\\": [\\"name\\", \\"description\\"], \\"customRanking\\": [\\"desc(popularity)\\"]})"},"forwardToReplicas":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to apply changes to replica indices (default: false)"}},"hostedApiKey":"none"},"amplitude_event_segmentation":{"id":"amplitude_event_segmentation","name":"Amplitude Event Segmentation","description":"Query event analytics data with segmentation. Get event counts, uniques, averages, and more.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"eventType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Event type name to analyze"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"metric":{"type":"string","required":false,"visibility":"user-or-llm","description":"Metric type: uniques, totals, pct_dau, average, histogram, sums, value_avg, or formula (default: uniques)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property name to group by (prefix custom user properties with \\"gp:\\")"},"groupBy2":{"type":"string","required":false,"visibility":"user-or-llm","description":"Second property name to group by (prefix custom user properties with \\"gp:\\")"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of group-by values (max 1000)"},"filters":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON array of filter objects applied to the event, e.g. [{\\"subprop_type\\":\\"event\\",\\"subprop_key\\":\\"city\\",\\"subprop_op\\":\\"is\\",\\"subprop_value\\":[\\"San Francisco\\"]}]"},"formula":{"type":"string","required":false,"visibility":"user-or-llm","description":"Required when metric is \\"formula\\", e.g. \\"UNIQUES(A)/UNIQUES(B)\\""},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_funnels":{"id":"amplitude_funnels","name":"Amplitude Funnels","description":"Analyze conversion rates and drop-off between a sequence of events.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"events":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON array of event objects, one per funnel step in order, e.g. [{\\"event_type\\":\\"signup\\"},{\\"event_type\\":\\"purchase\\"}]"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"mode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Funnel ordering: \\"ordered\\", \\"unordered\\", or \\"sequential\\" (default: ordered)"},"userType":{"type":"string","required":false,"visibility":"user-or-llm","description":"User type: \\"new\\" or \\"active\\" (default: active)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: -300000 (real-time), -3600000 (hourly), 1 (daily), 7 (weekly), or 30 (monthly)"},"conversionWindowSeconds":{"type":"string","required":false,"visibility":"user-or-llm","description":"Conversion window in seconds (default: 2592000, i.e. 30 days)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property to group by (limit: one; prefix custom properties with \\"gp:\\")"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of group-by values (default: 100, max: 1000)"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_get_active_users":{"id":"amplitude_get_active_users","name":"Amplitude Get Active Users","description":"Get active or new user counts over a date range from the Dashboard REST API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"metric":{"type":"string","required":false,"visibility":"user-or-llm","description":"Metric type: \\"active\\" or \\"new\\" (default: active)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property name to group by"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_get_revenue":{"id":"amplitude_get_revenue","name":"Amplitude Get Revenue","description":"Get revenue LTV data including ARPU, ARPPU, total revenue, and paying user counts.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"metric":{"type":"string","required":false,"visibility":"user-or-llm","description":"Metric: 0 (ARPU), 1 (ARPPU), 2 (Total Revenue), 3 (Paying Users)"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property name to group by (limit: one)"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_group_identify":{"id":"amplitude_group_identify","name":"Amplitude Group Identify","description":"Set group-level properties in Amplitude. Supports $set, $setOnce, $add, $append, $unset operations.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"groupType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Group classification (e.g., \\"company\\", \\"org_id\\")"},"groupValue":{"type":"string","required":true,"visibility":"user-or-llm","description":"Specific group identifier (e.g., \\"Acme Corp\\")"},"groupProperties":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON object of group properties. Use operations like $set, $setOnce, $add, $append, $unset."},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_identify_user":{"id":"amplitude_identify_user","name":"Amplitude Identify User","description":"Set user properties in Amplitude using the Identify API. Supports $set, $setOnce, $add, $append, $unset operations.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"userId":{"type":"string","required":false,"visibility":"user-or-llm","description":"User ID (required if no device_id)"},"deviceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Device ID (required if no user_id)"},"userProperties":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON object of user properties. Use operations like $set, $setOnce, $add, $append, $unset."},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_list_events":{"id":"amplitude_list_events","name":"Amplitude List Events","description":"List all event types in the Amplitude project with their weekly totals and unique counts.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_realtime_active_users":{"id":"amplitude_realtime_active_users","name":"Amplitude Real-time Active Users","description":"Get real-time active user counts at 5-minute granularity for the last 2 days.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_retention":{"id":"amplitude_retention","name":"Amplitude Retention","description":"Measure how many users return to perform an action after a starting action.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"startEvent":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON starting event object, e.g. {\\"event_type\\":\\"_new\\"} or {\\"event_type\\":\\"_active\\"}"},"returnEvent":{"type":"string","required":true,"visibility":"user-or-llm","description":"JSON returning event object, e.g. {\\"event_type\\":\\"_all\\"} or {\\"event_type\\":\\"_active\\"}"},"start":{"type":"string","required":true,"visibility":"user-or-llm","description":"Start date in YYYYMMDD format"},"end":{"type":"string","required":true,"visibility":"user-or-llm","description":"End date in YYYYMMDD format"},"retentionMode":{"type":"string","required":false,"visibility":"user-or-llm","description":"Retention type: \\"bracket\\", \\"rolling\\", or \\"n-day\\" (default: n-day)"},"retentionBrackets":{"type":"string","required":false,"visibility":"user-or-llm","description":"Required when Retention Mode is \\"bracket\\". Day ranges, e.g. [[0,4]]"},"interval":{"type":"string","required":false,"visibility":"user-or-llm","description":"Time interval: 1 (daily), 7 (weekly), or 30 (monthly)"},"groupBy":{"type":"string","required":false,"visibility":"user-or-llm","description":"Property to group by (limit: one; prefix custom properties with \\"gp:\\")"},"segment":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON segment definition(s) applied to the query"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_send_event":{"id":"amplitude_send_event","name":"Amplitude Send Event","description":"Track an event in Amplitude using the HTTP V2 API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"userId":{"type":"string","required":false,"visibility":"user-or-llm","description":"User ID (required if no device_id)"},"deviceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Device ID (required if no user_id)"},"eventType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the event (e.g., \\"page_view\\", \\"purchase\\")"},"eventProperties":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON object of custom event properties"},"userProperties":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON object of user properties to set (supports $set, $setOnce, $add, $append, $unset)"},"time":{"type":"string","required":false,"visibility":"user-or-llm","description":"Event timestamp in milliseconds since epoch"},"sessionId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Session start time in milliseconds since epoch"},"insertId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Unique ID for deduplication (within 7-day window)"},"appVersion":{"type":"string","required":false,"visibility":"user-or-llm","description":"Application version string"},"platform":{"type":"string","required":false,"visibility":"user-or-llm","description":"Platform (e.g., \\"Web\\", \\"iOS\\", \\"Android\\")"},"country":{"type":"string","required":false,"visibility":"user-or-llm","description":"Two-letter country code"},"language":{"type":"string","required":false,"visibility":"user-or-llm","description":"Language code (e.g., \\"en\\")"},"ip":{"type":"string","required":false,"visibility":"user-or-llm","description":"IP address for geo-location"},"price":{"type":"string","required":false,"visibility":"user-or-llm","description":"Price of the item purchased"},"quantity":{"type":"string","required":false,"visibility":"user-or-llm","description":"Quantity of items purchased"},"revenue":{"type":"string","required":false,"visibility":"user-or-llm","description":"Revenue amount"},"productId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Product identifier"},"revenueType":{"type":"string","required":false,"visibility":"user-or-llm","description":"Revenue type (e.g., \\"purchase\\", \\"refund\\")"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_user_activity":{"id":"amplitude_user_activity","name":"Amplitude User Activity","description":"Get the event stream for a specific user by their Amplitude ID.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"amplitudeId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Amplitude internal user ID"},"offset":{"type":"string","required":false,"visibility":"user-or-llm","description":"Offset for pagination (default 0)"},"limit":{"type":"string","required":false,"visibility":"user-or-llm","description":"Maximum number of events to return (default 1000, max 1000)"},"direction":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort direction: \\"latest\\" or \\"earliest\\" (default: latest)"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"amplitude_user_profile":{"id":"amplitude_user_profile","name":"Amplitude User Profile","description":"Get a user profile including properties, cohort memberships, and computed properties. Not available for EU data-residency projects.","version":"1.0.0","params":{"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"userId":{"type":"string","required":false,"visibility":"user-or-llm","description":"External user ID (required if no device_id)"},"deviceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Device ID (required if no user_id)"},"getAmpProps":{"type":"string","required":false,"visibility":"user-or-llm","description":"Include Amplitude user properties (true/false, default: false)"},"getCohortIds":{"type":"string","required":false,"visibility":"user-or-llm","description":"Include cohort IDs the user belongs to (true/false, default: false)"},"getComputations":{"type":"string","required":false,"visibility":"user-or-llm","description":"Include computed user properties (true/false, default: false)"}},"hostedApiKey":"none"},"amplitude_user_search":{"id":"amplitude_user_search","name":"Amplitude User Search","description":"Search for a user by User ID, Device ID, or Amplitude ID using the Dashboard REST API.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude API Key"},"secretKey":{"type":"string","required":true,"visibility":"user-only","description":"Amplitude Secret Key"},"user":{"type":"string","required":true,"visibility":"user-or-llm","description":"User ID, Device ID, or Amplitude ID to search for"},"dataResidency":{"type":"string","required":false,"visibility":"user-or-llm","description":"Data residency region: \\"us\\" (default) or \\"eu\\""}},"hostedApiKey":"none"},"apify_get_dataset_items":{"id":"apify_get_dataset_items","name":"APIFY Get Dataset Items","description":"Retrieve items stored in an APIFY dataset","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"datasetId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Dataset ID to read items from. Example: \\"9RnD3Pql2vGZkc5H5\\""},"itemLimit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Max items to return (1-250000). Default: all items. Example: 500"},"offset":{"type":"number","required":false,"visibility":"user-or-llm","description":"Number of items to skip at the start. Default: 0"},"fields":{"type":"string","required":false,"visibility":"user-or-llm","description":"Comma-separated list of fields to include. Example: \\"title,url,price\\""}},"hostedApiKey":"none"},"apify_get_run":{"id":"apify_get_run","name":"APIFY Get Run","description":"Get the status and details of an APIFY actor run","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"runId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Actor run ID to fetch. Example: \\"HG7ML7M8z78YcAPEB\\""}},"hostedApiKey":"none"},"apify_run_actor_async":{"id":"apify_run_actor_async","name":"APIFY Run Actor (Async)","description":"Run an APIFY actor asynchronously with polling for long-running tasks","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"actorId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Actor ID or username/actor-name. Examples: \\"apify/web-scraper\\", \\"janedoe/my-actor\\", \\"moJRLRc85AitArpNN\\""},"input":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor input as JSON string. Example: { \\"startUrls\\": [ { \\"url\\": \\"https://example.com\\" } ], \\"maxPages\\": 10 }"},"waitForFinish":{"type":"number","required":false,"visibility":"user-or-llm","description":"Initial wait time in seconds (0-60) before polling starts. Example: 30"},"itemLimit":{"type":"number","required":false,"default":100,"visibility":"user-or-llm","description":"Max dataset items to fetch (1-250000). Default: 100. Example: 500"},"memory":{"type":"number","required":false,"visibility":"user-or-llm","description":"Memory in megabytes allocated for the actor run (128-32768). Example: 1024 for 1GB, 2048 for 2GB"},"timeout":{"type":"number","required":false,"visibility":"user-or-llm","description":"Timeout in seconds for the actor run. Example: 300 for 5 minutes, 3600 for 1 hour"},"build":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor build to run. Examples: \\"latest\\", \\"beta\\", \\"1.2.3\\", \\"build-tag-name\\""}},"hostedApiKey":"none"},"apify_run_actor_sync":{"id":"apify_run_actor_sync","name":"APIFY Run Actor (Sync)","description":"Run an APIFY actor synchronously and get results (max 5 minutes)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"actorId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Actor ID or username/actor-name. Examples: \\"apify/web-scraper\\", \\"janedoe/my-actor\\", \\"moJRLRc85AitArpNN\\""},"input":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor input as JSON string. Example: { \\"startUrls\\": [ { \\"url\\": \\"https://example.com\\" } ], \\"maxPages\\": 10 }"},"memory":{"type":"number","required":false,"visibility":"user-or-llm","description":"Memory in megabytes allocated for the actor run (128-32768). Example: 1024 for 1GB, 2048 for 2GB"},"timeout":{"type":"number","required":false,"visibility":"user-or-llm","description":"Timeout in seconds for the actor run. Example: 300 for 5 minutes, 3600 for 1 hour"},"build":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor build to run. Examples: \\"latest\\", \\"beta\\", \\"1.2.3\\", \\"build-tag-name\\""}},"hostedApiKey":"none"},"apify_run_task":{"id":"apify_run_task","name":"APIFY Run Task","description":"Run a saved APIFY actor task synchronously and get dataset items (max 5 minutes)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"APIFY API token from console.apify.com/account#/integrations"},"taskId":{"type":"string","required":true,"visibility":"user-or-llm","description":"Task ID or username/task-name. Examples: \\"janedoe/my-task\\", \\"moJRLRc85AitArpNN\\""},"input":{"type":"string","required":false,"visibility":"user-or-llm","description":"JSON string that overrides the task\'s saved input. Example: { \\"startUrls\\": [ { \\"url\\": \\"https://example.com\\" } ] }"},"itemLimit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Max dataset items to return (1-250000). Example: 500"},"memory":{"type":"number","required":false,"visibility":"user-or-llm","description":"Memory in megabytes allocated for the run (128-32768). Example: 1024 for 1GB"},"timeout":{"type":"number","required":false,"visibility":"user-or-llm","description":"Timeout in seconds for the run. Example: 300 for 5 minutes"},"build":{"type":"string","required":false,"visibility":"user-or-llm","description":"Actor build to run. Examples: \\"latest\\", \\"beta\\", \\"1.2.3\\""}},"hostedApiKey":"none"},"apollo_account_bulk_create":{"id":"apollo_account_bulk_create","name":"Apollo Bulk Create Accounts","description":"Create up to 100 accounts at once in your Apollo database. Set run_dedupe=true to deduplicate by domain, organization_id, and name. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"accounts":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of accounts to create (max 100). Each account should include a name, and may optionally include domain, phone, phone_status_cd, raw_address, owner_id, linkedin_url, facebook_url, twitter_url, salesforce_id, and hubspot_id."},"append_label_names":{"type":"array","required":false,"visibility":"user-only","description":"Array of label names to add to ALL accounts in this request"},"run_dedupe":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"When true, performs aggressive deduplication by domain, organization_id, and name (defaults to false)"}},"hostedApiKey":"none"},"apollo_account_bulk_update":{"id":"apollo_account_bulk_update","name":"Apollo Bulk Update Accounts","description":"Update up to 1000 existing accounts at once in your Apollo database (higher limit than contacts!). Each account must include an id field. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"account_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of account IDs to update with the same values (max 1000). Use with name/owner_id for uniform updates. Use either this OR account_attributes."},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"When using account_ids, apply this name to all accounts"},"owner_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"When using account_ids, apply this owner to all accounts"},"account_stage_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"When using account_ids, apply this account stage to all accounts"},"account_attributes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of account objects with individual updates (each must include id). Example: [{\\"id\\": \\"acc1\\", \\"name\\": \\"Acme\\", \\"owner_id\\": \\"u1\\", \\"account_stage_id\\": \\"s1\\", \\"typed_custom_fields\\": {\\"field_id\\": \\"value\\"}}]"},"async":{"type":"boolean","required":false,"visibility":"user-only","description":"When true, processes the update asynchronously. Only supported when using account_ids; returns 422 if used with account_attributes."}},"hostedApiKey":"none"},"apollo_account_create":{"id":"apollo_account_create","name":"Apollo Create Account","description":"Create a new account (company) in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Company name (e.g., \\"Acme Corporation\\")"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company domain without www. prefix (e.g., \\"acme.com\\")"},"phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number for the account"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo user ID of the account owner"},"account_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo ID for the account stage to assign this account to"},"raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate location (e.g., \\"San Francisco, CA, USA\\")"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}},"hostedApiKey":"none"},"apollo_account_search":{"id":"apollo_account_search","name":"Apollo Search Accounts","description":"Search your team\'s accounts in Apollo. Display limit: 50,000 records (100 records per page, 500 pages max). Use filters to narrow results. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"q_organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter accounts by organization name (partial-match search)"},"account_stage_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by account stage IDs"},"account_label_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by account label IDs"},"sort_by_field":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort field: \\"account_last_activity_date\\", \\"account_created_at\\", or \\"account_updated_at\\""},"sort_ascending":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Sort ascending when true. Defaults to descending."},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_account_update":{"id":"apollo_account_update","name":"Apollo Update Account","description":"Update an existing account in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"account_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the account to update (e.g., \\"acc_abc123\\")"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company name (e.g., \\"Acme Corporation\\")"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company domain (e.g., \\"acme.com\\")"},"phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company phone number"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo user ID of the account owner"},"account_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"Apollo ID for the account stage to assign this account to"},"raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate location (e.g., \\"San Francisco, CA, USA\\")"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}},"hostedApiKey":"none"},"apollo_contact_bulk_create":{"id":"apollo_contact_bulk_create","name":"Apollo Bulk Create Contacts","description":"Create up to 100 contacts at once in your Apollo database. Supports deduplication to prevent creating duplicate contacts. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"contacts":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of contacts to create (max 100). Each contact may include first_name, last_name, email, title, organization_name, account_id, owner_id, contact_stage_id, linkedin_url, phone (single string) or phone_numbers (array of {raw_number, position}), contact_emails, typed_custom_fields, and CRM IDs (salesforce_contact_id, hubspot_id, team_id) for cross-system matching"},"append_label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Label names to add to all contacts in this request (e.g., [\\"Hot Lead\\"])"},"run_dedupe":{"type":"boolean","required":false,"visibility":"user-only","description":"Enable deduplication to prevent creating duplicate contacts. When true, existing contacts are returned without modification"}},"hostedApiKey":"none"},"apollo_contact_bulk_update":{"id":"apollo_contact_bulk_update","name":"Apollo Bulk Update Contacts","description":"Update up to 100 existing contacts at once in your Apollo database. Each contact must include an id field. Master key required.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"contact_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of contact IDs to update. Must be paired with an object-form contact_attributes specifying the fields to apply uniformly to all listed contacts."},"contact_attributes":{"type":"json","required":false,"visibility":"user-or-llm","description":"Required. Either an array of per-contact updates (each with id) — used standalone — or a single object of attributes to apply to all contact_ids. Supported fields: owner_id, email, organization_name, title, first_name, last_name, account_id, present_raw_address, linkedin_url, typed_custom_fields"},"async":{"type":"boolean","required":false,"visibility":"user-only","description":"Force asynchronous processing. Automatically enabled for >100 contacts"}},"hostedApiKey":"none"},"apollo_contact_create":{"id":"apollo_contact_create","name":"Apollo Create Contact","description":"Create a new contact in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"first_name":{"type":"string","required":true,"visibility":"user-or-llm","description":"First name of the contact"},"last_name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Last name of the contact"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Email address of the contact"},"title":{"type":"string","required":false,"visibility":"user-or-llm","description":"Job title (e.g., \\"VP of Sales\\", \\"Software Engineer\\")"},"account_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo account ID to associate with (e.g., \\"acc_abc123\\")"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the contact owner (accepted by Apollo but not officially documented for POST /contacts)"},"organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name of the contact\'s employer (e.g., \\"Apollo\\")"},"website_url":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate website URL (e.g., \\"https://www.apollo.io/\\")"},"label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Lists/labels to add the contact to (e.g., [\\"Prospects\\"])"},"contact_stage_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo ID for the contact stage"},"present_raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Personal location for the contact (e.g., \\"Atlanta, United States\\")"},"direct_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number"},"corporate_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Work/office phone number"},"mobile_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Mobile phone number"},"home_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Home phone number"},"other_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Alternative phone number"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-or-llm","description":"Custom field values keyed by custom field ID"},"run_dedupe":{"type":"boolean","required":false,"visibility":"user-only","description":"When true, Apollo deduplicates against existing contacts"}},"hostedApiKey":"none"},"apollo_contact_search":{"id":"apollo_contact_search","name":"Apollo Search Contacts","description":"Search your team\'s contacts in Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"q_keywords":{"type":"string","required":false,"visibility":"user-or-llm","description":"Keywords to search for"},"contact_stage_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by contact stage IDs"},"contact_label_ids":{"type":"array","required":false,"visibility":"user-only","description":"Filter by Apollo label IDs (lists)"},"sort_by_field":{"type":"string","required":false,"visibility":"user-only","description":"Sort field: contact_last_activity_date, contact_email_last_opened_at, contact_email_last_clicked_at, contact_created_at, or contact_updated_at"},"sort_ascending":{"type":"boolean","required":false,"visibility":"user-only","description":"When true, sort ascending. Must be used together with sort_by_field"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_contact_update":{"id":"apollo_contact_update","name":"Apollo Update Contact","description":"Update an existing contact in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"contact_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the contact to update (e.g., \\"con_abc123\\")"},"first_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"First name of the contact"},"last_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Last name of the contact"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Email address"},"title":{"type":"string","required":false,"visibility":"user-or-llm","description":"Job title (e.g., \\"VP of Sales\\", \\"Software Engineer\\")"},"account_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo account ID (e.g., \\"acc_abc123\\")"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the contact owner (accepted by Apollo but not officially documented for PATCH /contacts/{id})"},"organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name of the contact\'s employer (e.g., \\"Apollo\\")"},"website_url":{"type":"string","required":false,"visibility":"user-or-llm","description":"Corporate website URL (e.g., \\"https://www.apollo.io/\\")"},"label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Lists/labels to add the contact to (e.g., [\\"Prospects\\"])"},"contact_stage_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo ID for the contact stage"},"present_raw_address":{"type":"string","required":false,"visibility":"user-or-llm","description":"Personal location for the contact (e.g., \\"Atlanta, United States\\")"},"direct_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number"},"corporate_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Work/office phone number"},"mobile_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Mobile phone number"},"home_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Home phone number"},"other_phone":{"type":"string","required":false,"visibility":"user-or-llm","description":"Alternative phone number"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-or-llm","description":"Custom field values keyed by custom field ID"}},"hostedApiKey":"none"},"apollo_email_accounts":{"id":"apollo_email_accounts","name":"Apollo Get Email Accounts","description":"Get list of team\'s linked email accounts in Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"}},"hostedApiKey":"none"},"apollo_opportunity_create":{"id":"apollo_opportunity_create","name":"Apollo Create Opportunity","description":"Create a new deal for an account in your Apollo database (master key required)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the opportunity/deal (e.g., \\"Enterprise License - Q1\\")"},"account_id":{"type":"string","required":false,"visibility":"user-or-llm","description":"ID of the account this opportunity belongs to (e.g., \\"acc_abc123\\")"},"amount":{"type":"string","required":false,"visibility":"user-or-llm","description":"Monetary value as a plain number string with no commas or currency symbols"},"opportunity_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"ID of the opportunity stage"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the opportunity owner"},"closed_date":{"type":"string","required":false,"visibility":"user-or-llm","description":"Expected close date in YYYY-MM-DD format"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}},"hostedApiKey":"none"},"apollo_opportunity_get":{"id":"apollo_opportunity_get","name":"Apollo Get Opportunity","description":"Retrieve complete details of a specific deal/opportunity by ID","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"opportunity_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the opportunity to retrieve (e.g., \\"opp_abc123\\")"}},"hostedApiKey":"none"},"apollo_opportunity_search":{"id":"apollo_opportunity_search","name":"Apollo Search Opportunities","description":"Search and list all deals/opportunities in your team\'s Apollo account","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"sort_by_field":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort field: \\"amount\\", \\"is_closed\\", or \\"is_won\\""},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_opportunity_update":{"id":"apollo_opportunity_update","name":"Apollo Update Opportunity","description":"Update an existing deal/opportunity in your Apollo database","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"opportunity_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the opportunity to update (e.g., \\"opp_abc123\\")"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Name of the opportunity/deal (e.g., \\"Enterprise License - Q1\\")"},"amount":{"type":"string","required":false,"visibility":"user-or-llm","description":"Monetary value as a plain number string with no commas or currency symbols"},"opportunity_stage_id":{"type":"string","required":false,"visibility":"user-only","description":"ID of the opportunity stage"},"owner_id":{"type":"string","required":false,"visibility":"user-only","description":"User ID of the opportunity owner"},"closed_date":{"type":"string","required":false,"visibility":"user-or-llm","description":"Expected close date in YYYY-MM-DD format"},"typed_custom_fields":{"type":"json","required":false,"visibility":"user-only","description":"Custom field values as { custom_field_id: value } map"}},"hostedApiKey":"none"},"apollo_organization_bulk_enrich":{"id":"apollo_organization_bulk_enrich","name":"Apollo Bulk Organization Enrichment","description":"Enrich data for up to 10 organizations at once using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"domains":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of company domains to enrich (max 10, no www. or @, e.g., [\\"apollo.io\\", \\"stripe.com\\"])"}},"hostedApiKey":"none"},"apollo_organization_enrich":{"id":"apollo_organization_enrich","name":"Apollo Organization Enrichment","description":"Enrich data for a single organization using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"domain":{"type":"string","required":true,"visibility":"user-or-llm","description":"Company domain (e.g., \\"apollo.io\\", \\"acme.com\\")"}},"hostedApiKey":"none"},"apollo_organization_search":{"id":"apollo_organization_search","name":"Apollo Organization Search","description":"Search Apollo\'s database for companies using filters","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"organization_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Company HQ locations (cities, US states, or countries)"},"organization_not_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Exclude companies whose HQ is in these locations"},"organization_num_employees_ranges":{"type":"array","required":false,"visibility":"user-or-llm","description":"Employee count ranges as \\"min,max\\" strings (e.g., [\\"1,10\\", \\"250,500\\", \\"10000,20000\\"])"},"q_organization_keyword_tags":{"type":"array","required":false,"visibility":"user-or-llm","description":"Industry or keyword tags"},"q_organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Organization name to search for (e.g., \\"Acme\\", \\"TechCorp\\")"},"organization_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Apollo organization IDs to include (e.g., [\\"5e66b6381e05b4008c8331b8\\"])"},"q_organization_domains_list":{"type":"array","required":false,"visibility":"user-or-llm","description":"Domain names to filter by (no www. or @, up to 1,000)"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_people_bulk_enrich":{"id":"apollo_people_bulk_enrich","name":"Apollo Bulk People Enrichment","description":"Enrich data for up to 10 people at once using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"people":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of people to enrich (max 10)"},"reveal_personal_emails":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal personal email addresses (uses credits)"},"reveal_phone_number":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal phone numbers (uses credits, requires webhook_url)"},"webhook_url":{"type":"string","required":false,"visibility":"user-only","description":"Webhook URL for async phone number delivery (required when reveal_phone_number is true)"}},"hostedApiKey":"none"},"apollo_people_enrich":{"id":"apollo_people_enrich","name":"Apollo People Enrichment","description":"Enrich data for a single person using Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"first_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"First name of the person"},"last_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Last name of the person"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Full name of the person (alternative to first_name/last_name)"},"id":{"type":"string","required":false,"visibility":"user-or-llm","description":"Apollo ID for the person"},"hashed_email":{"type":"string","required":false,"visibility":"user-or-llm","description":"MD5 or SHA-256 hashed email"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Email address of the person"},"organization_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company name where the person works"},"domain":{"type":"string","required":false,"visibility":"user-or-llm","description":"Company domain (e.g., \\"apollo.io\\", \\"acme.com\\")"},"linkedin_url":{"type":"string","required":false,"visibility":"user-or-llm","description":"LinkedIn profile URL"},"reveal_personal_emails":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal personal email addresses (uses credits)"},"reveal_phone_number":{"type":"boolean","required":false,"visibility":"user-only","description":"Reveal phone numbers (uses credits, requires webhook_url)"},"webhook_url":{"type":"string","required":false,"visibility":"user-only","description":"Webhook URL for async phone number delivery (required when reveal_phone_number is true)"}},"hostedApiKey":"none"},"apollo_people_search":{"id":"apollo_people_search","name":"Apollo People Search","description":"Search Apollo\'s database for people using demographic filters","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key"},"person_titles":{"type":"array","required":false,"visibility":"user-or-llm","description":"Job titles to search for (e.g., [\\"CEO\\", \\"VP of Sales\\"])"},"include_similar_titles":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Whether to return people with job titles similar to person_titles"},"person_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Locations to search in (e.g., [\\"San Francisco, CA\\", \\"New York, NY\\"])"},"person_seniorities":{"type":"array","required":false,"visibility":"user-or-llm","description":"Seniority levels (one of: owner, founder, c_suite, partner, vp, head, director, manager, senior, entry, intern)"},"organization_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Apollo organization IDs to filter by (e.g., [\\"5e66b6381e05b4008c8331b8\\"])"},"organization_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Company names to search within (legacy filter)"},"organization_locations":{"type":"array","required":false,"visibility":"user-or-llm","description":"Headquarters locations of the people\'s current employer (e.g., [\'texas\', \'tokyo\', \'spain\'])"},"q_organization_domains_list":{"type":"array","required":false,"visibility":"user-or-llm","description":"Employer domain names (e.g., [\\"apollo.io\\", \\"microsoft.com\\"]) — up to 1,000, no www. or @"},"organization_num_employees_ranges":{"type":"array","required":false,"visibility":"user-or-llm","description":"Employee count ranges for the person\'s current employer. Each entry is \\"min,max\\" (e.g., [\\"1,10\\", \\"250,500\\", \\"10000,20000\\"])"},"contact_email_status":{"type":"array","required":false,"visibility":"user-or-llm","description":"Email statuses to filter by: \\"verified\\", \\"unverified\\", \\"likely to engage\\", \\"unavailable\\""},"q_keywords":{"type":"string","required":false,"visibility":"user-or-llm","description":"Keywords to search for"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination, default 1 (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, default 25, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_sequence_add_contacts":{"id":"apollo_sequence_add_contacts","name":"Apollo Add Contacts to Sequence","description":"Add contacts to an Apollo sequence","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"sequence_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the sequence to add contacts to (e.g., \\"seq_abc123\\")"},"contact_ids":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of contact IDs to add to the sequence (e.g., [\\"con_abc123\\", \\"con_def456\\"]). Either contact_ids or label_names must be provided."},"label_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of label names to identify contacts to add to the sequence. Either contact_ids or label_names must be provided."},"send_email_from_email_account_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the email account to send from. Use the Get Email Accounts operation to look this up."},"send_email_from_email_address":{"type":"string","required":false,"visibility":"user-only","description":"Specific email address to send from within the email account."},"sequence_no_email":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts even if they have no email address"},"sequence_unverified_email":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts with unverified email addresses"},"sequence_job_change":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts who recently changed jobs"},"sequence_active_in_other_campaigns":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts active in other campaigns"},"sequence_finished_in_other_campaigns":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts who finished other campaigns"},"sequence_same_company_in_same_campaign":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts even if others from the same company are in the sequence"},"contacts_without_ownership_permission":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts without ownership permission"},"add_if_in_queue":{"type":"boolean","required":false,"visibility":"user-only","description":"Add contacts even if they are in the queue"},"contact_verification_skipped":{"type":"boolean","required":false,"visibility":"user-only","description":"Skip contact verification when adding"},"user_id":{"type":"string","required":false,"visibility":"user-only","description":"ID of the user performing the action"},"status":{"type":"string","required":false,"visibility":"user-only","description":"Initial status for added contacts: \\"active\\" or \\"paused\\""},"auto_unpause_at":{"type":"string","required":false,"visibility":"user-only","description":"ISO 8601 datetime to automatically unpause contacts"}},"hostedApiKey":"none"},"apollo_sequence_search":{"id":"apollo_sequence_search","name":"Apollo Search Sequences","description":"Search for sequences/campaigns in your team\'s Apollo account (master key required)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"q_name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Search sequences by name (e.g., \\"Outbound Q1\\", \\"Follow-up\\")"},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"apollo_task_create":{"id":"apollo_task_create","name":"Apollo Create Task","description":"Create one or more tasks in Apollo (one task per contact_id, master key required)","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"user_id":{"type":"string","required":true,"visibility":"user-or-llm","description":"ID of the Apollo user the task is assigned to"},"contact_ids":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of contact IDs. One task is created per contact."},"priority":{"type":"string","required":false,"visibility":"user-or-llm","description":"Task priority: \\"high\\", \\"medium\\", or \\"low\\" (defaults to \\"medium\\")"},"due_at":{"type":"string","required":true,"visibility":"user-or-llm","description":"Due date/time in ISO 8601 format (e.g., \\"2024-12-31T23:59:59Z\\")"},"type":{"type":"string","required":true,"visibility":"user-or-llm","description":"Task type: \\"call\\", \\"outreach_manual_email\\", \\"linkedin_step_connect\\", \\"linkedin_step_message\\", \\"linkedin_step_view_profile\\", \\"linkedin_step_interact_post\\", or \\"action_item\\""},"status":{"type":"string","required":true,"visibility":"user-or-llm","description":"Task status: \\"scheduled\\", \\"completed\\", or \\"skipped\\""},"note":{"type":"string","required":false,"visibility":"user-or-llm","description":"Free-form note providing context for the task"}},"hostedApiKey":"none"},"apollo_task_search":{"id":"apollo_task_search","name":"Apollo Search Tasks","description":"Search for tasks in Apollo","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Apollo API key (master key required)"},"sort_by_field":{"type":"string","required":false,"visibility":"user-or-llm","description":"Sort field: \\"task_due_at\\" or \\"task_priority\\""},"open_factor_names":{"type":"array","required":false,"visibility":"user-or-llm","description":"Filter by status. Common values: [\\"task_types\\"] for open tasks, [\\"task_completed_at\\"] for completed tasks."},"page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Page number for pagination (e.g., 1, 2, 3)"},"per_page":{"type":"number","required":false,"visibility":"user-or-llm","description":"Results per page, max 100 (e.g., 25, 50, 100)"}},"hostedApiKey":"none"},"appconfig_create_application":{"id":"appconfig_create_application","name":"AppConfig Create Application","description":"Create an application in AWS AppConfig","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the application to create"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the application"}},"hostedApiKey":"none"},"appconfig_create_configuration_profile":{"id":"appconfig_create_configuration_profile","name":"AppConfig Create Configuration Profile","description":"Create a configuration profile in an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to create the configuration profile in"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the configuration profile"},"locationUri":{"type":"string","required":true,"visibility":"user-or-llm","description":"Where the configuration is stored. Use \\"hosted\\" for AppConfig-hosted configurations, or an SSM/S3 URI"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the configuration profile"},"retrievalRoleArn":{"type":"string","required":false,"visibility":"user-or-llm","description":"ARN of an IAM role to retrieve the configuration (required for non-hosted URIs)"},"type":{"type":"string","required":false,"visibility":"user-or-llm","description":"Profile type: AWS.Freeform (default) or AWS.AppConfig.FeatureFlags"}},"hostedApiKey":"none"},"appconfig_create_environment":{"id":"appconfig_create_environment","name":"AppConfig Create Environment","description":"Create an environment for an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to create the environment in"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the environment to create"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the environment"}},"hostedApiKey":"none"},"appconfig_create_hosted_configuration_version":{"id":"appconfig_create_hosted_configuration_version","name":"AppConfig Create Hosted Configuration Version","description":"Create a new hosted configuration version for an AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to add the version to"},"content":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration content (e.g., a JSON or YAML document)"},"contentType":{"type":"string","required":true,"visibility":"user-or-llm","description":"Content type of the configuration (e.g., application/json, text/plain)"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the configuration version"},"latestVersionNumber":{"type":"number","required":false,"visibility":"user-or-llm","description":"The version number of the latest version, used for optimistic concurrency"},"versionLabel":{"type":"string","required":false,"visibility":"user-or-llm","description":"A user-defined label for the configuration version"}},"hostedApiKey":"none"},"appconfig_delete_application":{"id":"appconfig_delete_application","name":"AppConfig Delete Application","description":"Delete an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to delete"}},"hostedApiKey":"none"},"appconfig_delete_configuration_profile":{"id":"appconfig_delete_configuration_profile","name":"AppConfig Delete Configuration Profile","description":"Delete an AWS AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to delete"}},"hostedApiKey":"none"},"appconfig_delete_environment":{"id":"appconfig_delete_environment","name":"AppConfig Delete Environment","description":"Delete an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to delete"}},"hostedApiKey":"none"},"appconfig_delete_hosted_configuration_version":{"id":"appconfig_delete_hosted_configuration_version","name":"AppConfig Delete Hosted Configuration Version","description":"Delete a specific hosted configuration version from an AppConfig profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID that owns the version"},"versionNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The version number to delete"}},"hostedApiKey":"none"},"appconfig_get_application":{"id":"appconfig_get_application","name":"AppConfig Get Application","description":"Get details about a single AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to retrieve"}},"hostedApiKey":"none"},"appconfig_get_configuration":{"id":"appconfig_get_configuration","name":"AppConfig Get Configuration","description":"Retrieve the latest deployed configuration for an AppConfig application, environment, and profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID or name to retrieve configuration for"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID or name to retrieve configuration for"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID or name to retrieve"}},"hostedApiKey":"none"},"appconfig_get_configuration_profile":{"id":"appconfig_get_configuration_profile","name":"AppConfig Get Configuration Profile","description":"Get details about a single AWS AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to retrieve"}},"hostedApiKey":"none"},"appconfig_get_deployment":{"id":"appconfig_get_deployment","name":"AppConfig Get Deployment","description":"Get details about a specific AWS AppConfig deployment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID of the deployment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID of the deployment"},"deploymentNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The sequence number of the deployment"}},"hostedApiKey":"none"},"appconfig_get_environment":{"id":"appconfig_get_environment","name":"AppConfig Get Environment","description":"Get details about a single AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to retrieve"}},"hostedApiKey":"none"},"appconfig_get_hosted_configuration_version":{"id":"appconfig_get_hosted_configuration_version","name":"AppConfig Get Hosted Configuration Version","description":"Retrieve a specific hosted configuration version from an AppConfig profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to read the version from"},"versionNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The version number to retrieve"}},"hostedApiKey":"none"},"appconfig_list_applications":{"id":"appconfig_list_applications","name":"AppConfig List Applications","description":"List applications in AWS AppConfig","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of applications to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_list_configuration_profiles":{"id":"appconfig_list_configuration_profiles","name":"AppConfig List Configuration Profiles","description":"List configuration profiles for an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profiles"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of configuration profiles to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_list_deployment_strategies":{"id":"appconfig_list_deployment_strategies","name":"AppConfig List Deployment Strategies","description":"List deployment strategies available in AWS AppConfig","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of deployment strategies to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_list_deployments":{"id":"appconfig_list_deployments","name":"AppConfig List Deployments","description":"List deployments for an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID of the deployments"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID of the deployments"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of deployments to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_list_environments":{"id":"appconfig_list_environments","name":"AppConfig List Environments","description":"List environments for an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environments"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of environments to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_list_hosted_configuration_versions":{"id":"appconfig_list_hosted_configuration_versions","name":"AppConfig List Hosted Configuration Versions","description":"List hosted configuration versions for an AWS AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to list versions for"},"maxResults":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of versions to return (1-50)"},"nextToken":{"type":"string","required":false,"visibility":"user-or-llm","description":"Pagination token from a previous response"}},"hostedApiKey":"none"},"appconfig_start_deployment":{"id":"appconfig_start_deployment","name":"AppConfig Start Deployment","description":"Start deploying a configuration version to an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to deploy in"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to deploy to"},"deploymentStrategyId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The deployment strategy ID to use"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to deploy"},"configurationVersion":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration version to deploy"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"Description of the deployment"}},"hostedApiKey":"none"},"appconfig_stop_deployment":{"id":"appconfig_stop_deployment","name":"AppConfig Stop Deployment","description":"Stop an in-progress AWS AppConfig deployment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID of the deployment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID of the deployment"},"deploymentNumber":{"type":"number","required":true,"visibility":"user-or-llm","description":"The sequence number of the deployment to stop"}},"hostedApiKey":"none"},"appconfig_update_application":{"id":"appconfig_update_application","name":"AppConfig Update Application","description":"Update the name or description of an AWS AppConfig application","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New name for the application"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"New description for the application"}},"hostedApiKey":"none"},"appconfig_update_configuration_profile":{"id":"appconfig_update_configuration_profile","name":"AppConfig Update Configuration Profile","description":"Update the name, description, or retrieval role of an AppConfig configuration profile","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the configuration profile"},"configurationProfileId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The configuration profile ID to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New name for the configuration profile"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"New description for the configuration profile"},"retrievalRoleArn":{"type":"string","required":false,"visibility":"user-or-llm","description":"New ARN of the IAM role used to retrieve the configuration"}},"hostedApiKey":"none"},"appconfig_update_environment":{"id":"appconfig_update_environment","name":"AppConfig Update Environment","description":"Update the name or description of an AWS AppConfig environment","version":"1.0","params":{"region":{"type":"string","required":true,"visibility":"user-only","description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","required":true,"visibility":"user-only","description":"AWS access key ID"},"secretAccessKey":{"type":"string","required":true,"visibility":"user-only","description":"AWS secret access key"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The application ID that owns the environment"},"environmentId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The environment ID to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"New name for the environment"},"description":{"type":"string","required":false,"visibility":"user-or-llm","description":"New description for the environment"}},"hostedApiKey":"none"},"arxiv_get_author_papers":{"id":"arxiv_get_author_papers","name":"ArXiv Get Author Papers","description":"Search for papers by a specific author on ArXiv.","version":"1.0.0","params":{"authorName":{"type":"string","required":true,"visibility":"user-or-llm","description":"Author name to search for"},"maxResults":{"type":"number","required":false,"visibility":"user-only","description":"Maximum number of results to return (default: 10, max: 2000)"}},"hostedApiKey":"none"},"arxiv_get_paper":{"id":"arxiv_get_paper","name":"ArXiv Get Paper","description":"Get detailed information about a specific ArXiv paper by its ID.","version":"1.0.0","params":{"paperId":{"type":"string","required":true,"visibility":"user-or-llm","description":"ArXiv paper ID (e.g., \\"1706.03762\\")"}},"hostedApiKey":"none"},"arxiv_search":{"id":"arxiv_search","name":"ArXiv Search","description":"Search for academic papers on ArXiv by keywords, authors, titles, or other fields.","version":"1.0.0","params":{"searchQuery":{"type":"string","required":true,"visibility":"user-or-llm","description":"The search query to execute"},"searchField":{"type":"string","required":false,"visibility":"user-only","description":"Field to search in: all, ti (title), au (author), abs (abstract), co (comment), jr (journal), cat (category), rn (report number)"},"maxResults":{"type":"number","required":false,"visibility":"user-only","description":"Maximum number of results to return (default: 10, max: 2000)"},"sortBy":{"type":"string","required":false,"visibility":"user-only","description":"Sort by: relevance, lastUpdatedDate, submittedDate (default: relevance)"},"sortOrder":{"type":"string","required":false,"visibility":"user-only","description":"Sort order: ascending, descending (default: descending)"}},"hostedApiKey":"none"},"asana_add_comment":{"id":"asana_add_comment","name":"Asana Add Comment","description":"Add a comment (story) to an Asana task","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana task GID (numeric string)"},"text":{"type":"string","required":true,"visibility":"user-or-llm","description":"The text content of the comment"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_add_followers":{"id":"asana_add_followers","name":"Asana Add Followers","description":"Add one or more followers to an Asana task","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana task (numeric string)"},"followers":{"type":"array","required":true,"visibility":"user-or-llm","description":"Array of user GIDs to add as followers to the task"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_create_project":{"id":"asana_create_project","name":"Asana Create Project","description":"Create a new project in an Asana workspace","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) where the project will be created"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the project"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Notes or description for the project"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_create_section":{"id":"asana_create_section","name":"Asana Create Section","description":"Create a new section in an Asana project","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"projectGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana project (numeric string) to add the section to"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the section"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_create_subtask":{"id":"asana_create_subtask","name":"Asana Create Subtask","description":"Create a subtask under an existing Asana task","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the parent Asana task (numeric string)"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the subtask"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Notes or description for the subtask"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"User GID to assign the subtask to"},"due_on":{"type":"string","required":false,"visibility":"user-or-llm","description":"Due date in YYYY-MM-DD format"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_create_task":{"id":"asana_create_task","name":"Asana Create Task","description":"Create a new task in Asana","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) where the task will be created"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"Name of the task"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Notes or description for the task"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"User GID to assign the task to"},"due_on":{"type":"string","required":false,"visibility":"user-or-llm","description":"Due date in YYYY-MM-DD format"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_delete_task":{"id":"asana_delete_task","name":"Asana Delete Task","description":"Delete an Asana task by its GID (moves it to the trash)","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana task to delete (numeric string)"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_get_project":{"id":"asana_get_project","name":"Asana Get Project","description":"Retrieve a single Asana project by its GID","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"projectGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana project GID (numeric string) to retrieve"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_get_projects":{"id":"asana_get_projects","name":"Asana Get Projects","description":"Retrieve all projects from an Asana workspace","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) to retrieve projects from"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_get_task":{"id":"asana_get_task","name":"Asana Get Task","description":"Retrieve a single task by GID or get multiple tasks with filters","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":false,"visibility":"user-or-llm","description":"The globally unique identifier (GID) of the task. If not provided, will get multiple tasks."},"workspace":{"type":"string","required":false,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) to filter tasks (required when not using taskGid)"},"project":{"type":"string","required":false,"visibility":"user-or-llm","description":"Asana project GID (numeric string) to filter tasks"},"limit":{"type":"number","required":false,"visibility":"user-or-llm","description":"Maximum number of tasks to return (default: 50)"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_list_sections":{"id":"asana_list_sections","name":"Asana List Sections","description":"List all sections in an Asana project","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"projectGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"GID of the Asana project (numeric string) to list sections from"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_list_workspaces":{"id":"asana_list_workspaces","name":"Asana List Workspaces","description":"List all Asana workspaces and organizations the authenticated user belongs to","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_search_tasks":{"id":"asana_search_tasks","name":"Asana Search Tasks","description":"Search for tasks in an Asana workspace","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"workspace":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana workspace GID (numeric string) to search tasks in"},"text":{"type":"string","required":false,"visibility":"user-or-llm","description":"Text to search for in task names"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"Filter tasks by assignee user GID"},"projects":{"type":"array","required":false,"visibility":"user-or-llm","description":"Array of Asana project GIDs (numeric strings) to filter tasks by"},"completed":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Filter by completion status"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"asana_update_task":{"id":"asana_update_task","name":"Asana Update Task","description":"Update an existing task in Asana","version":"1.0.0","params":{"accessToken":{"type":"string","required":true,"visibility":"hidden","description":"OAuth access token for Asana"},"taskGid":{"type":"string","required":true,"visibility":"user-or-llm","description":"Asana task GID (numeric string) of the task to update"},"name":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated name for the task"},"notes":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated notes or description for the task"},"assignee":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated assignee user GID"},"completed":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Mark task as completed or not completed"},"due_on":{"type":"string","required":false,"visibility":"user-or-llm","description":"Updated due date in YYYY-MM-DD format"}},"oauth":{"required":true,"provider":"asana"},"hostedApiKey":"none"},"ashby_add_candidate_tag":{"id":"ashby_add_candidate_tag","name":"Ashby Add Candidate Tag","description":"Adds a tag to a candidate in Ashby and returns the updated candidate.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the candidate to add the tag to"},"tagId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the tag to add"}},"hostedApiKey":"none"},"ashby_anonymize_candidate":{"id":"ashby_anonymize_candidate","name":"Ashby Anonymize Candidate","description":"Strips personally identifiable information from a candidate in Ashby. This does not delete the candidate - the record and its applications remain, with the PII removed. Ashby exposes no candidate deletion endpoint; true deletion is UI-only, restricted by role, and limited to a 10-day window. Requires the candidatesWrite permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"UUID of the candidate to anonymize"}},"hostedApiKey":"none"},"ashby_change_application_source":{"id":"ashby_change_application_source","name":"Ashby Change Application Source","description":"Changes the source attributed to an existing application, so programmatically created applications report correctly on the recruiting side. Requires the candidatesWrite permission.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"UUID of the application whose source should change"},"sourceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the source to attribute the application to, as returned by List Sources. Omit only when unsetSource is true."},"unsetSource":{"type":"boolean","required":false,"visibility":"user-or-llm","description":"Set true to deliberately clear the application source. Required to unset, so that a missing or empty sourceId cannot wipe attribution by accident."}},"hostedApiKey":"none"},"ashby_change_application_stage":{"id":"ashby_change_application_stage","name":"Ashby Change Application Stage","description":"Moves an application to a different interview stage. Requires an archive reason when moving to an Archived stage.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"applicationId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the application to update the stage of"},"interviewStageId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the interview stage to move the application to"},"archiveReasonId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Archive reason UUID. Required when moving to an Archived stage, ignored otherwise"},"archiveEmail":{"type":"json","required":false,"visibility":"user-or-llm","description":"Archive email configuration with communicationTemplateId and optional sendAt ISO 8601 timestamp. Pass null or omit to send no archive email."}},"hostedApiKey":"none"},"ashby_create_application":{"id":"ashby_create_application","name":"Ashby Create Application","description":"Creates a new application for a candidate on a job. Optionally specify interview plan, stage, source, and credited user.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the candidate to consider for the job"},"jobId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the job to consider the candidate for"},"interviewPlanId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the interview plan to use (defaults to the job default plan)"},"interviewStageId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the interview stage to place the application in, or FirstPreInterviewScreen (defaults to the first Lead stage)"},"sourceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the source to set on the application"},"creditedToUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the user the application is credited to"},"createdAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"ISO 8601 timestamp to set as the application creation date (defaults to now)"},"applicationHistory":{"type":"json","required":false,"visibility":"user-or-llm","description":"Optional documented application history entries to create with the application"}},"hostedApiKey":"none"},"ashby_create_candidate":{"id":"ashby_create_candidate","name":"Ashby Create Candidate","description":"Creates a new candidate record in Ashby.","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"name":{"type":"string","required":true,"visibility":"user-or-llm","description":"The candidate full name"},"email":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary email address for the candidate"},"phoneNumber":{"type":"string","required":false,"visibility":"user-or-llm","description":"Primary phone number for the candidate"},"linkedInUrl":{"type":"string","required":false,"visibility":"user-or-llm","description":"LinkedIn profile URL"},"githubUrl":{"type":"string","required":false,"visibility":"user-or-llm","description":"GitHub profile URL"},"website":{"type":"string","required":false,"visibility":"user-or-llm","description":"Personal website URL"},"sourceId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the source to attribute the candidate to"},"creditedToUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"UUID of the Ashby user to credit with sourcing this candidate"},"createdAt":{"type":"string","required":false,"visibility":"user-or-llm","description":"Backdated creation timestamp in ISO 8601 (e.g. 2024-01-01T00:00:00Z). Defaults to now."},"alternateEmailAddresses":{"type":"json","required":false,"visibility":"user-or-llm","description":"Array of additional email address strings to add to the candidate, e.g. [\\"a@x.com\\",\\"b@y.com\\"]"},"location":{"type":"json","required":false,"visibility":"user-or-llm","description":"Candidate location object with optional city, region, and country"}},"hostedApiKey":"none"},"ashby_create_note":{"id":"ashby_create_note","name":"Ashby Create Note","description":"Creates a note on a candidate in Ashby. Supports plain text and HTML content (bold, italic, underline, links, lists, code).","version":"1.0.0","params":{"apiKey":{"type":"string","required":true,"visibility":"user-only","description":"Ashby API Key"},"onBehalfOfUserId":{"type":"string","required":false,"visibility":"user-or-llm","description":"Active Ashby user UUID to attribute this mutation to; the API key must permit on-behalf-of calls"},"candidateId":{"type":"string","required":true,"visibility":"user-or-llm","description":"The UUID of the candidate to add the note to"},"note":{"type":"string","required":true,"visibility":"user-or-llm","description":"The note content. If noteType is text/html, supports: , , , ,