From c4576e09faebf7638ab59cf62b4d0c0e61479a17 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 17 Sep 2026 11:44:20 -0700 Subject: [PATCH 1/4] fix(search): resume Google crawls around unavailable accounts --- .github/workflows/test-build.yml | 5 + apps/docs/content/docs/search/gmail.mdx | 2 +- .../content/docs/search/google-calendar.mdx | 6 +- .../docs/content/docs/search/google-drive.mdx | 3 +- .../google-drive/company-crawl.test.ts | 8 + .../connectors/google-drive/company-crawl.ts | 20 +- .../google-drive/google-drive-errors.ts | 14 +- .../google-workspace/api-errors.test.ts | 58 + .../connectors/google-workspace/api-errors.ts | 44 +- .../google-workspace/company-crawl.test.ts | 1 + .../google-workspace/company-crawl.ts | 38 +- .../google-workspace/company-work.ts | 13 + apps/sim/connectors/listing-failures.test.ts | 56 + apps/sim/connectors/listing-failures.ts | 2 + apps/sim/connectors/source-error.ts | 17 +- apps/sim/connectors/types.ts | 8 +- .../connector-lifecycle-locks.integration.ts | 262 + .../google-company-work.integration.ts | 296 + .../listing-continuation.integration.ts | 228 +- .../stored-document-recovery.integration.ts | 86 +- .../lib/knowledge/application/connectors.ts | 6 +- .../knowledge/connectors/access-token.test.ts | 38 + .../lib/knowledge/connectors/access-token.ts | 27 +- .../connectors/connector-error.test.ts | 23 +- .../knowledge/connectors/connector-error.ts | 2 + .../google-company-scheduler.test.ts | 445 + .../connectors/google-company-scheduler.ts | 410 + .../connectors/google-company-store.ts | 189 + .../connectors/listing-checkpoint.ts | 21 +- .../connectors/member-sync-engine.ts | 2 +- .../connectors/sync-content-pass.test.ts | 121 +- .../knowledge/connectors/sync-content-pass.ts | 127 +- .../lib/knowledge/connectors/sync-deferral.ts | 2 +- .../knowledge/connectors/sync-engine.test.ts | 2 +- .../lib/knowledge/connectors/sync-engine.ts | 9 +- .../knowledge/connectors/sync-primitives.ts | 2 +- .../documents/ocr-source-validation.test.ts | 41 + .../documents/ocr-source-validation.ts | 14 + .../documents/pdf-ocr-triage.test.ts | 34 + .../documents/processing-recovery.ts | 38 +- apps/sim/lib/oauth/credential-service.test.ts | 75 +- apps/sim/lib/oauth/credential-service.ts | 53 +- .../google-service-account-transport.test.ts | 191 + .../oauth/google-service-account-transport.ts | 79 + .../0356_google_company_user_progress.sql | 27 + .../db/migrations/meta/0356_snapshot.json | 27507 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 54 + packages/testing/src/mocks/schema.mock.ts | 19 + 49 files changed, 30640 insertions(+), 92 deletions(-) create mode 100644 apps/sim/connectors/google-workspace/company-work.ts create mode 100644 apps/sim/connectors/listing-failures.test.ts create mode 100644 apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts create mode 100644 apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts create mode 100644 apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts create mode 100644 apps/sim/lib/knowledge/connectors/google-company-scheduler.ts create mode 100644 apps/sim/lib/knowledge/connectors/google-company-store.ts create mode 100644 apps/sim/lib/oauth/google-service-account-transport.test.ts create mode 100644 apps/sim/lib/oauth/google-service-account-transport.ts create mode 100644 packages/db/migrations/0356_google_company_user_progress.sql create mode 100644 packages/db/migrations/meta/0356_snapshot.json diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 66694c9d63d..323941874ad 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -235,6 +235,11 @@ jobs: lib/knowledge/__integration__/search-source-pagination.integration.ts lib/knowledge/__integration__/search-reference-batching.integration.ts lib/knowledge/__integration__/embedding-insert-batches.integration.ts + lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts + lib/knowledge/__integration__/connector-deferral.integration.ts + lib/knowledge/__integration__/stored-document-recovery.integration.ts + lib/knowledge/__integration__/google-company-work.integration.ts + lib/knowledge/__integration__/listing-continuation.integration.ts lib/knowledge/__integration__/kb-block-search.integration.ts lib/core/outbox/service.integration.ts lib/knowledge/__integration__/connector-upload.integration.ts diff --git a/apps/docs/content/docs/search/gmail.mdx b/apps/docs/content/docs/search/gmail.mdx index ecb646f4e65..b796b3160d7 100644 --- a/apps/docs/content/docs/search/gmail.mdx +++ b/apps/docs/content/docs/search/gmail.mdx @@ -133,7 +133,7 @@ Search schedules syncs hourly. The first sync lists every thread in scope and ca **Member accounts:** later syncs use each mailbox's Gmail change history, unless the configuration has a search filter. A full relisting runs about weekly, or sooner if Gmail no longer retains the saved history. -**Service account:** each sync revisits the selected active mailboxes and resumes unfinished listings. If Google reports that a user's mailbox is not set up or returns a mailbox `failedPrecondition`, Sim records a warning and continues with the remaining users. The crawl stays incomplete and retries affected users on the next scheduled crawl; existing indexed mail is not deleted because a mailbox could not be read. Credential, delegation, and Directory failures still stop the crawl. +**Service account:** each user's progress is saved separately, so a large mailbox or an account-specific access failure does not hold up other users. Temporary failures retry automatically. Unavailable mailboxes are revisited without deleting indexed mail solely because the mailbox could not be read. Sim continues discovering users and refreshing existing mail and permissions while unfinished work resumes. Results with stale or unverified permissions remain hidden. Credential, delegation, Directory, and scope failures still require attention. Updates, removals, and access refresh in the background. Empty mailboxes and filters with no matches complete normally with zero documents. Threads exceeding indexing size limits are skipped and reconsidered when they change. diff --git a/apps/docs/content/docs/search/google-calendar.mdx b/apps/docs/content/docs/search/google-calendar.mdx index 817e7acf770..b16a3111978 100644 --- a/apps/docs/content/docs/search/google-calendar.mdx +++ b/apps/docs/content/docs/search/google-calendar.mdx @@ -62,7 +62,7 @@ Admins can request member connections from **Settings → Sources → People → Open **Settings → Sources → Add source** and select **Google Calendar**. This opens **Connect Google Calendar service account**. To add another connection later, open **Google Calendar** from the Sources list and select **Connect service account**. -This requires a Google Workspace customer and a super administrator to authorize domain-wide delegation. Consumer Gmail accounts cannot use this path. +This requires a Google Workspace customer and a super administrator to authorize domain-wide delegation. Consumer Gmail accounts cannot use this path. In the Google Admin Console, confirm **Apps → Google Workspace → Calendar → Service status** is on for the users you want to index. An active Workspace account does not necessarily have Calendar enabled. @@ -131,7 +131,9 @@ Sim indexes event titles, descriptions, times, locations, and the selected atten Cancelled events, attachment contents, meeting recordings, and transcripts are not indexed. Status entries such as working location, out of office, focus time, and birthdays, and automatically generated reservation events from Gmail are not indexed. Events Google returns only as free/busy blocks, without searchable details, are not indexed. Events outside the selected date window are excluded. Private event details that Google withholds are not available in Search; see [Google's calendar sharing rules](https://developers.google.com/workspace/calendar/api/concepts/sharing). -Search schedules syncs hourly. Event edits, cancellations, access changes, inactive or removed users, and events moving outside the date window are reconciled during completed background syncs. Central crawls page through each selected user and resume unfinished work before removing documents no longer listed. If an individual user's event listing returns a `403` with an explicit `forbidden` reason and no other reasons, Sim records a warning and continues with the remaining users. The crawl stays incomplete and retries affected users on the next scheduled crawl; unread calendars are not treated as empty. A `403` without a reason stops the crawl because its cause is unknown. Credential, delegation, Directory, and other provider failures still stop the crawl. The first sync may take longer, and results appear as indexing progresses; Search is not a live Calendar read. +Search schedules syncs hourly. The first sync may span several background runs; results appear as indexing progresses. Central crawls save each user's progress so a large calendar or an account-specific access failure does not hold up other users. Temporary failures retry automatically. Unresolved accounts are revisited without treating unread calendars as empty; credential, delegation, Directory, and scope failures still require attention. + +Event edits, cancellations, access changes, inactive or removed users, and events moving outside the date window are reconciled in the background. Sim refreshes existing permissions while a longer crawl continues. Results with stale or unverified permissions remain hidden. Search is not a live Calendar read. ## Troubleshooting diff --git a/apps/docs/content/docs/search/google-drive.mdx b/apps/docs/content/docs/search/google-drive.mdx index 2dd2d0237e9..725d1cb7fcd 100644 --- a/apps/docs/content/docs/search/google-drive.mdx +++ b/apps/docs/content/docs/search/google-drive.mdx @@ -151,7 +151,7 @@ Use **Sync now** to request an update, **Pause syncing** to stop scheduled syncs Sim exports Docs and Slides as text and Sheets as XLSX spreadsheets. Supported uploaded files use the knowledge-base document pipeline, including PDF and Office formats. Unsupported files and oversized exports cannot be indexed; Google limits Workspace exports to 10 MB. See [Drive export formats](https://developers.google.com/workspace/drive/api/guides/ref-export-formats) and [download limits](https://developers.google.com/workspace/drive/api/guides/manage-downloads). -Search schedules syncs hourly. Central crawls revisit the selected users' files and permissions, including unchanged files, so permission changes and a new employee's older files are included. Unfinished crawls resume before deletion reconciliation. Content, deletions, and permissions refresh in the background; results are not a live read from Drive. **People** on the Sources settings page shows personal account connections; it does not list the central service-account credential. +Search schedules syncs hourly. Central crawls save each user's progress separately, so a large Drive or an account-specific access failure does not hold up other users. Temporary failures retry automatically. Sim continues discovering users and refreshing files and permissions while unfinished work resumes. Unread files are not treated as deleted, and results with stale or unverified permissions remain hidden. Results are not a live read from Drive. **People** on the Sources settings page shows personal account connections; it does not list the central service-account credential. ## Troubleshooting @@ -163,6 +163,7 @@ Directory permission warnings mean Sim could not fully verify group membership. | 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. | | No text could be extracted | Images and scanned PDFs use OCR, but files with no extractable text cannot be indexed. Check whether the original contains readable text; a successful download does not guarantee searchable content. | +| File contains a Git LFS pointer | The uploaded file references content stored elsewhere instead of containing the actual image or PDF. Replace it in Drive with the original file content, then sync again. Retrying the unchanged pointer cannot recover the content. | | 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. | | A teammate sees no results | Confirm they have joined the Sim organization and their verified Sim email matches the Drive permission or group membership. For member accounts, finish their personal Drive connection too. | | A public or shared-link file is missing | Check **Openly shared files**. Link-only sharing does not grant Search access. A named user or group permission can still make the file searchable. | diff --git a/apps/sim/connectors/google-drive/company-crawl.test.ts b/apps/sim/connectors/google-drive/company-crawl.test.ts index 1633a255f98..79bc5e73ddb 100644 --- a/apps/sim/connectors/google-drive/company-crawl.test.ts +++ b/apps/sim/connectors/google-drive/company-crawl.test.ts @@ -553,6 +553,14 @@ describe('Google Drive company-wide crawl', () => { ).rejects.toThrow() expect(mockFetch).not.toHaveBeenCalled() }) + + it('passes the crawl signal into the delegated token exchange', async () => { + fixture({}) + const controller = new AbortController() + const ctx = { ...context(), signal: controller.signal } + await drive.listDocuments('directory-token', CONFIG, undefined, ctx) + expect(ctx.getDelegatedAccessToken).toHaveBeenCalledWith('alice@corp.com', controller.signal) + }) }) describe('Company-wide setup validation', () => { diff --git a/apps/sim/connectors/google-drive/company-crawl.ts b/apps/sim/connectors/google-drive/company-crawl.ts index 4a4815bc424..d56d9d7c6cb 100644 --- a/apps/sim/connectors/google-drive/company-crawl.ts +++ b/apps/sim/connectors/google-drive/company-crawl.ts @@ -6,6 +6,7 @@ import { GOOGLE_WORKSPACE_DRIVES_PAGE_SIZE, listGoogleWorkspaceDrives, } from '@/connectors/google-drive/workspace-drives' +import type { GoogleCompanyCursorAdapter } from '@/connectors/google-workspace/company-work' import { GOOGLE_WORKSPACE_USERS_PAGE_SIZE, getGoogleWorkspaceUser, @@ -52,7 +53,7 @@ const cursorSchema = z.object({ }) type CompanyCursor = z.infer -type DelegatedTokenResolver = (subject: string) => Promise +type DelegatedTokenResolver = (subject: string, signal?: AbortSignal) => Promise function delegatedTokenResolver(syncContext: Record): DelegatedTokenResolver { if (typeof syncContext.getDelegatedAccessToken !== 'function') { @@ -88,6 +89,21 @@ function writeCursor(cursor: CompanyCursor): string { return serialized } +/** A single-user cursor preserves shared-drive traversal as well as the user's own Drive page. */ +export const googleDriveCompanyCursorAdapter: GoogleCompanyCursorAdapter = { + seed: (user) => writeCursor({ users: [user], scope: { kind: 'user' } }), + resume: (cursor) => { + const state = readCursor(cursor) + return state.users.map((user, index) => ({ + user, + cursor: writeCursor({ + users: [user], + scope: index === 0 ? state.scope : { kind: 'user' }, + }), + })) + }, +} + /** Positive caps can stop before later users; central sources must finish their selected corpus. */ function validateCompanyConfig(sourceConfig: Record): string[] { if ( @@ -197,7 +213,7 @@ export async function listGoogleCompanyDocuments(input: { } signal?.throwIfAborted() - const userToken = await resolveToken(user.email) + const userToken = await (signal ? resolveToken(user.email, signal) : resolveToken(user.email)) const nextDrives = async (pageToken?: string): Promise => { const drives = await listGoogleWorkspaceDrives(userToken, pageToken, signal) if (drives.driveIds.length > 0) { diff --git a/apps/sim/connectors/google-drive/google-drive-errors.ts b/apps/sim/connectors/google-drive/google-drive-errors.ts index 141a0195906..b26749a4155 100644 --- a/apps/sim/connectors/google-drive/google-drive-errors.ts +++ b/apps/sim/connectors/google-drive/google-drive-errors.ts @@ -12,6 +12,7 @@ import { import { ConnectorSourceError, type ConnectorSourceFailureCategory, + type ConnectorSourceReasonState, } from '@/connectors/source-error' const GOOGLE_ERROR_REASON_MAX_COUNT = 16 @@ -110,7 +111,8 @@ export class GoogleDriveApiError extends ConnectorSourceError { status: number, normalizedReasons: readonly string[], operation = 'drive.request', - reasonsComplete = true + reasonsComplete = true, + reasonState?: ConnectorSourceReasonState ) { const diagnosticReasons = safeGoogleErrorReasons(normalizedReasons).slice( 0, @@ -122,7 +124,7 @@ export class GoogleDriveApiError extends ConnectorSourceError { `Google Drive API request failed with HTTP ${status}${reasonSuffix}`, status, diagnosticCategory(kind, status), - { operation, reasons: diagnosticReasons } + { operation, reasons: diagnosticReasons, ...(reasonState ? { reasonState } : {}) } ) this.name = 'GoogleDriveApiError' this.reasons = diagnosticReasons @@ -144,7 +146,13 @@ export async function readGoogleDriveApiError( operation = 'drive.request' ): Promise { const details = await readGoogleErrorDetails(response) - return new GoogleDriveApiError(response.status, details.reasons, operation, details.complete) + return new GoogleDriveApiError( + response.status, + details.reasons, + operation, + details.complete, + details.reasonState + ) } /** diff --git a/apps/sim/connectors/google-workspace/api-errors.test.ts b/apps/sim/connectors/google-workspace/api-errors.test.ts index 04ca5ec08ce..fd14c8efcae 100644 --- a/apps/sim/connectors/google-workspace/api-errors.test.ts +++ b/apps/sim/connectors/google-workspace/api-errors.test.ts @@ -32,6 +32,7 @@ describe('Google API diagnostics', () => { new Error('outer private detail', { cause: error }) ) expect(diagnostic).toMatchObject({ status, category, operation: OPERATION, reasons: [reason] }) + expect(diagnostic?.reasonState).toBe('present') expect(JSON.stringify(diagnostic)).not.toContain(RESPONSE_SECRET) expect(JSON.stringify(diagnostic)).not.toContain('outer private detail') }) @@ -40,6 +41,7 @@ describe('Google API diagnostics', () => { const error = await readGoogleApiError(failure(403, RESPONSE_SECRET), OPERATION) expect(error.diagnostic?.reasons).toEqual([]) expect(error.reasonsComplete).toBe(false) + expect(getConnectorFailureDiagnostic(error)?.reasonState).toBe('filtered') expect(JSON.stringify(error)).not.toContain(RESPONSE_SECRET) }) @@ -82,11 +84,13 @@ describe('Google API diagnostics', () => { ) expect(absent.reasonsComplete).toBe(true) expect(absent.diagnostic?.reasons).toEqual([]) + expect(getConnectorFailureDiagnostic(absent)?.reasonState).toBe('absent') const malformed = await readGoogleApiError( Response.json({ error: { errors: [{ reason: 123 }] } }, { status: 403 }), 'calendar.events.list' ) expect(malformed.reasonsComplete).toBe(false) + expect(getConnectorFailureDiagnostic(malformed)?.reasonState).toBe('malformed') const mixed = await readGoogleApiError( Response.json( { error: { errors: [{ reason: 'forbidden' }, { reason: RESPONSE_SECRET }] } }, @@ -96,6 +100,7 @@ describe('Google API diagnostics', () => { ) expect(mixed.diagnostic?.reasons).toEqual(['forbidden']) expect(mixed.reasonsComplete).toBe(false) + expect(getConnectorFailureDiagnostic(mixed)?.reasonState).toBe('filtered') expect(JSON.stringify([absent, malformed, mixed])).not.toContain(RESPONSE_SECRET) }) @@ -120,8 +125,61 @@ describe('Google API diagnostics', () => { ) expect(error.diagnostic?.reasons).toEqual(['forbidden']) expect(error.reasonsComplete).toBe(false) + expect(getConnectorFailureDiagnostic(error)?.reasonState).toBe('malformed') expect(JSON.stringify(error)).not.toContain(RESPONSE_SECRET) }) + + it('distinguishes an unreadable body from a malformed response', async () => { + const unreadable = new Response( + new ReadableStream({ + start(controller) { + controller.error(new Error(RESPONSE_SECRET)) + }, + }), + { status: 403 } + ) + const error = await readGoogleApiError(unreadable, OPERATION) + expect(getConnectorFailureDiagnostic(error)?.reasonState).toBe('unreadable') + expect(error.reasonsComplete).toBe(false) + expect(JSON.stringify(error)).not.toContain(RESPONSE_SECRET) + const malformed = await readGoogleApiError(new Response('not-json', { status: 403 }), OPERATION) + expect(getConnectorFailureDiagnostic(malformed)?.reasonState).toBe('malformed') + }) + + it('retains classification evidence beyond the diagnostic reason limit', async () => { + const reasons = [ + 'accessNotConfigured', + 'appNotAuthorizedToFile', + 'authError', + 'badRequest', + 'cannotDownloadFile', + 'cannotExportFile', + 'domainPolicy', + 'download_restricted_for_revision', + 'exportSizeLimitExceeded', + 'failedPrecondition', + 'fileNotDownloadable', + 'fileNotExportable', + 'forbidden', + 'insufficientFilePermissions', + 'insufficientPermissions', + 'invalid', + 'rateLimitExceeded', + ] + const error = await readGoogleApiError( + Response.json( + { + error: { errors: reasons.map((reason) => ({ reason })) }, + }, + { status: 403 } + ), + OPERATION + ) + expect(error.rateLimited).toBe(true) + expect(error.reasonsComplete).toBe(false) + expect(error.diagnostic?.reasonState).toBe('filtered') + expect(error.diagnostic?.reasons).toHaveLength(16) + }) }) describe('Google API retries', () => { diff --git a/apps/sim/connectors/google-workspace/api-errors.ts b/apps/sim/connectors/google-workspace/api-errors.ts index e5c16f75b02..c9f43e157b0 100644 --- a/apps/sim/connectors/google-workspace/api-errors.ts +++ b/apps/sim/connectors/google-workspace/api-errors.ts @@ -5,7 +5,7 @@ import { type RetryOptions, resolveRetryDelayMs, } from '@/lib/knowledge/documents/utils' -import { ConnectorSourceError } from '@/connectors/source-error' +import { ConnectorSourceError, type ConnectorSourceReasonState } from '@/connectors/source-error' import { readBodyWithLimit } from '@/connectors/utils' const ERROR_BODY_MAX_BYTES = 64 * 1024 @@ -60,15 +60,16 @@ export function safeGoogleErrorReasons(reasons: readonly string[]): string[] { /** Distinguishes a reasonless envelope from one whose reason codes cannot safely be interpreted. */ export async function readGoogleErrorDetails( response: Response -): Promise<{ reasons: string[]; complete: boolean }> { - const unreadable = { reasons: [], complete: false } +): Promise<{ reasons: string[]; complete: boolean; reasonState: ConnectorSourceReasonState }> { + const unreadable = { reasons: [], complete: false, reasonState: 'unreadable' as const } + const malformed = { reasons: [], complete: false, reasonState: 'malformed' as const } const body = await readBodyWithLimit(response, ERROR_BODY_MAX_BYTES).catch(() => null) if (!body) return unreadable try { const payload: unknown = JSON.parse(body.toString('utf8')) - if (!payload || typeof payload !== 'object' || !('error' in payload)) return unreadable + if (!payload || typeof payload !== 'object' || !('error' in payload)) return malformed const error = payload.error - if (!error || typeof error !== 'object' || Array.isArray(error)) return unreadable + if (!error || typeof error !== 'object' || Array.isArray(error)) return malformed const envelopeValid = (!('errors' in error) || Array.isArray(error.errors)) && (!('details' in error) || Array.isArray(error.details)) @@ -82,17 +83,24 @@ export async function readGoogleErrorDetails( : [] ) const safeReasons = safeGoogleErrorReasons(reasons) + const validReasons = + envelopeValid && + (entries.length > 0 || ('code' in error && error.code === response.status)) && + reasons.length === entries.length + const reasonState: ConnectorSourceReasonState = !validReasons + ? 'malformed' + : reasons.some((reason) => !SAFE_REASONS.has(reason)) || safeReasons.length > MAX_REASONS + ? 'filtered' + : reasons.length === 0 + ? 'absent' + : 'present' return { reasons: safeReasons, - complete: - envelopeValid && - (entries.length > 0 || ('code' in error && error.code === response.status)) && - reasons.length === entries.length && - reasons.every((reason) => SAFE_REASONS.has(reason)) && - safeReasons.length <= MAX_REASONS, + complete: reasonState === 'present' || reasonState === 'absent', + reasonState, } } catch { - return unreadable + return malformed } } @@ -104,7 +112,8 @@ export class GoogleApiError extends ConnectorSourceError { operation: string, status: number, reasons: readonly string[], - reasonsComplete = true + reasonsComplete = true, + reasonState?: ConnectorSourceReasonState ) { const safeReasons = safeGoogleErrorReasons(reasons) const suffix = safeReasons.length ? ` (${safeReasons.join(', ')})` : '' @@ -123,6 +132,7 @@ export class GoogleApiError extends ConnectorSourceError { super(`${operation} failed (HTTP ${status})${suffix}.`, status, category, { operation, reasons: safeReasons.slice(0, MAX_REASONS), + ...(reasonState ? { reasonState } : {}), }) this.name = 'GoogleApiError' this.reasonsComplete = @@ -139,7 +149,13 @@ export async function readGoogleApiError( operation: string ): Promise { const details = await readGoogleErrorDetails(response) - return new GoogleApiError(operation, response.status, details.reasons, details.complete) + return new GoogleApiError( + operation, + response.status, + details.reasons, + details.complete, + details.reasonState + ) } /** Preserves Google diagnostics when the shared transport retries a transient HTTP response. */ diff --git a/apps/sim/connectors/google-workspace/company-crawl.test.ts b/apps/sim/connectors/google-workspace/company-crawl.test.ts index 0c8c5c16041..cc4bfd3b715 100644 --- a/apps/sim/connectors/google-workspace/company-crawl.test.ts +++ b/apps/sim/connectors/google-workspace/company-crawl.test.ts @@ -349,6 +349,7 @@ describe('Google Workspace per-user central crawl', () => { } const calls = listUserDocuments.mock.calls.length await expect(list(ctx2, first.currentCursor)).rejects.toThrow() + expect(ctx2.getDelegatedAccessToken).toHaveBeenCalledWith('alice@corp.com', controller2.signal) expect(listUserDocuments).toHaveBeenCalledTimes(calls) }) diff --git a/apps/sim/connectors/google-workspace/company-crawl.ts b/apps/sim/connectors/google-workspace/company-crawl.ts index 3a354ad36f3..86eecd79432 100644 --- a/apps/sim/connectors/google-workspace/company-crawl.ts +++ b/apps/sim/connectors/google-workspace/company-crawl.ts @@ -3,6 +3,7 @@ import { normalizeEmail } from '@sim/utils/string' import { z } from 'zod' import { mapWithConcurrency } from '@/lib/core/utils/concurrency' import { GoogleApiError } from '@/connectors/google-workspace/api-errors' +import type { GoogleCompanyCursorAdapter } from '@/connectors/google-workspace/company-work' import { GOOGLE_WORKSPACE_USERS_PAGE_SIZE, type GoogleWorkspaceUser, @@ -91,15 +92,40 @@ function writeCursor(state: CompanyCursor): string { return cursor } +/** Reuses the verified per-user crawl without placing every user's continuation in one cursor. */ +export function googleWorkspaceCompanyCursorAdapter( + provider: GoogleWorkspaceProvider +): GoogleCompanyCursorAdapter { + return { + seed: (user) => writeCursor({ provider, users: [user] }), + resume: (cursor) => { + const state = readCursor(cursor, provider) + return state.users.map((user, index) => ({ + user, + cursor: writeCursor({ + provider, + users: [user], + ...(index === 0 ? { providerCursor: state.providerCursor } : {}), + }), + })) + }, + } +} + function signalFrom(context: Record): AbortSignal | undefined { return context.signal instanceof AbortSignal ? context.signal : undefined } -function tokenResolver(context: Record): (subject: string) => Promise { +function tokenResolver( + context: Record +): (subject: string, signal?: AbortSignal) => Promise { if (context.mirrorsSourceAcls !== true || typeof context.getDelegatedAccessToken !== 'function') { throw new Error('Company-wide indexing requires a delegated Google Workspace service account') } - return context.getDelegatedAccessToken as (subject: string) => Promise + return context.getDelegatedAccessToken as ( + subject: string, + signal?: AbortSignal + ) => Promise } function userContext( @@ -117,9 +143,11 @@ async function delegate( user: GoogleWorkspaceUser, context: Record ): Promise { - signalFrom(context)?.throwIfAborted() - const token = await tokenResolver(context)(user.email) - signalFrom(context)?.throwIfAborted() + const signal = signalFrom(context) + signal?.throwIfAborted() + const resolveToken = tokenResolver(context) + const token = await (signal ? resolveToken(user.email, signal) : resolveToken(user.email)) + signal?.throwIfAborted() if (typeof token !== 'string' || !token) throw new Error('Google Workspace delegation returned no access token') return token diff --git a/apps/sim/connectors/google-workspace/company-work.ts b/apps/sim/connectors/google-workspace/company-work.ts new file mode 100644 index 00000000000..012d45b09d1 --- /dev/null +++ b/apps/sim/connectors/google-workspace/company-work.ts @@ -0,0 +1,13 @@ +import type { GoogleWorkspaceUser } from '@/connectors/google-workspace/users' + +/** Directory identity and provider continuation are persisted separately from delegated credentials. */ +export interface GoogleCompanyUserWork { + user: Pick + cursor?: string +} + +export interface GoogleCompanyCursorAdapter { + seed: (user: GoogleCompanyUserWork['user']) => string + /** Retains the exact active page when adopting the former single-user-at-a-time cursor. */ + resume: (cursor: string) => GoogleCompanyUserWork[] +} diff --git a/apps/sim/connectors/listing-failures.test.ts b/apps/sim/connectors/listing-failures.test.ts new file mode 100644 index 00000000000..18fd0af498c --- /dev/null +++ b/apps/sim/connectors/listing-failures.test.ts @@ -0,0 +1,56 @@ +/** @vitest-environment node */ +import { describe, expect, it } from 'vitest' +import { + beginListingCheckpoint, + readListingCheckpoint, +} from '@/lib/knowledge/connectors/listing-checkpoint' +import { listingFailuresSchema } from '@/connectors/listing-failures' +import { CONNECTOR_SOURCE_REASON_STATES } from '@/connectors/source-error' + +const sample = { + scope: 'user@fixture.test', + operation: 'calendar.events.list', + status: 403, + reasons: [], +} + +describe('listing failure diagnostics', () => { + it.each(CONNECTOR_SOURCE_REASON_STATES)( + 'preserves %s reason evidence through durable checkpoint serialization', + (reasonState) => { + const fingerprint = 'a'.repeat(64) + const checkpoint = { + ...beginListingCheckpoint({ + fingerprint, + generationId: 'generation', + startedAt: new Date('2026-09-17T00:00:00Z'), + }), + listingFailures: { count: 1, samples: [{ ...sample, reasonState }] }, + } + expect( + readListingCheckpoint(structuredClone(checkpoint), fingerprint)?.listingFailures + ).toEqual(checkpoint.listingFailures) + } + ) + + it('accepts existing samples without reason state and rejects arbitrary new state strings', () => { + expect(listingFailuresSchema.parse({ count: 1, samples: [sample] })).toEqual({ + count: 1, + samples: [sample], + }) + expect( + listingFailuresSchema.safeParse({ + count: 1, + samples: [{ ...sample, reasonState: 'provider-private-description' }], + }).success + ).toBe(false) + }) + + it('retains the enum without persisting attached provider messages or response bodies', () => { + const result = listingFailuresSchema.parse({ + count: 1, + samples: [{ ...sample, reasonState: 'filtered', message: 'private', body: 'private' }], + }) + expect(result.samples[0]).toEqual({ ...sample, reasonState: 'filtered' }) + }) +}) diff --git a/apps/sim/connectors/listing-failures.ts b/apps/sim/connectors/listing-failures.ts index d64e85bfd82..22dedb2e9d1 100644 --- a/apps/sim/connectors/listing-failures.ts +++ b/apps/sim/connectors/listing-failures.ts @@ -1,4 +1,5 @@ import { z } from 'zod' +import { CONNECTOR_SOURCE_REASON_STATES } from '@/connectors/source-error' export const MAX_LISTING_FAILURE_SAMPLES = 10 @@ -12,6 +13,7 @@ export const listingFailuresSchema = z.object({ operation: z.string().min(1).max(96), status: z.number().int().min(100).max(599).optional(), reasons: z.array(z.string().min(1).max(64)).max(16), + reasonState: z.enum(CONNECTOR_SOURCE_REASON_STATES).optional(), }) ) .max(MAX_LISTING_FAILURE_SAMPLES), diff --git a/apps/sim/connectors/source-error.ts b/apps/sim/connectors/source-error.ts index 37e28189674..9b7f2184fc4 100644 --- a/apps/sim/connectors/source-error.ts +++ b/apps/sim/connectors/source-error.ts @@ -6,13 +6,28 @@ export type ConnectorSourceFailureCategory = | 'rate_limit' | 'provider_unavailable' +/** Whether provider reason codes were present and safe to retain. */ +export const CONNECTOR_SOURCE_REASON_STATES = [ + 'present', + 'absent', + 'filtered', + 'malformed', + 'unreadable', +] as const + +export type ConnectorSourceReasonState = (typeof CONNECTOR_SOURCE_REASON_STATES)[number] + /** Providers classify their structured reasons here; shared diagnostics own user-facing text. */ export class ConnectorSourceError extends Error { constructor( message: string, readonly status: number, readonly category?: ConnectorSourceFailureCategory, - readonly diagnostic?: { operation: string; reasons: readonly string[] } + readonly diagnostic?: { + operation: string + reasons: readonly string[] + reasonState?: ConnectorSourceReasonState + } ) { super(message) this.name = 'ConnectorSourceError' diff --git a/apps/sim/connectors/types.ts b/apps/sim/connectors/types.ts index 9586bad3c79..5c5bc3f49b0 100644 --- a/apps/sim/connectors/types.ts +++ b/apps/sim/connectors/types.ts @@ -3,6 +3,7 @@ import type { ConnectorAccessMode } from '@/lib/knowledge/connectors/access-mode import type { ConnectorPermissionConfigCapability } from '@/lib/knowledge/connectors/permission-config' import type { OAuthService } from '@/lib/oauth/types' import type { SelectorKey } from '@/lib/selectors/manifest' +import type { ConnectorSourceReasonState } from '@/connectors/source-error' /** * Authentication configuration for a connector. @@ -190,6 +191,7 @@ export interface ExternalListingFailures { operation: string status?: number reasons: string[] + reasonState?: ConnectorSourceReasonState }[] } @@ -207,7 +209,11 @@ export interface ExternalDocumentList { */ reconciliationSafe?: boolean /** Cumulative, bounded failure evidence for this listing generation; replay must not add it twice. */ - listingFailures?: ExternalListingFailures + listingFailures?: ExternalListingFailures | null + /** Refreshes existing permissions and repairs changed stored bodies, without discovering new content or reconciling absence. */ + permissionsOnly?: boolean + /** A durable user-work queue can yield until the next bounded retry becomes due. */ + resumeAt?: string } /** diff --git a/apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts new file mode 100644 index 00000000000..8d59264625a --- /dev/null +++ b/apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts @@ -0,0 +1,262 @@ +/** Real PostgreSQL contention and deletion exclusion through source lifecycle entry points. */ +import { db } from '@sim/db' +import { + document, + knowledgeBase, + knowledgeConnector, + knowledgeConnectorSyncLog, + organization, + user, + workspace, +} from '@sim/db/schema' +import { generateId } from '@sim/utils/id' +import { and, eq, inArray, isNotNull, sql } from 'drizzle-orm' +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type { DbTransaction } from '@/lib/db/types' + +vi.mock('@/lib/knowledge/documents/service', async (original) => ({ + ...(await original()), + processDocumentsWithQueue: vi.fn(async (documents: unknown[]) => ({ + requested: documents.length, + accepted: documents.length, + failed: 0, + })), +})) + +import { resolveBillingAttribution } from '@/lib/billing/core/billing-attribution' +import { + createKnowledgeAclFixtureIds, + seedKnowledgeAclFixture, +} from '@/lib/knowledge/__integration__/seed-source-access-fixture' +import { deferConnectorSync } from '@/lib/knowledge/connectors/sync-deferral' +import { completeSuccessfulSync } from '@/lib/knowledge/connectors/sync-engine' +import { createContentSyncLease } from '@/lib/knowledge/connectors/sync-lock' +import { sweepStuckDocuments } from '@/lib/knowledge/connectors/sync-primitives' +import { deleteKnowledgeBase } from '@/lib/knowledge/service' +import { GitHubRequestDeferredError } from '@/connectors/github/request' +import type { SyncResult } from '@/connectors/types' + +const WAIT_OPTIONS = { interval: 5, timeout: 5000 } +const ACTIONS = ['complete', 'defer', 'recover'] as const +type Action = (typeof ACTIONS)[number] + +function deferred() { + let resolve!: () => void + const promise = new Promise((ready) => { + resolve = ready + }) + return { promise, resolve } +} + +function emptyResult(): SyncResult { + return { + docsAdded: 0, + docsUpdated: 0, + docsDeleted: 0, + docsUnchanged: 0, + docsSkipped: 0, + docsFailed: 0, + processingDispatch: { requested: 0, accepted: 0, failed: 0 }, + } +} + +describe('source lifecycle KB guards', () => { + let ids: ReturnType + let billing: Awaited> + let retryDocumentId: string + + beforeEach(async () => { + ids = createKnowledgeAclFixtureIds() + await seedKnowledgeAclFixture(ids) + billing = await resolveBillingAttribution({ + actorUserId: ids.aliceId, + workspaceId: ids.workspaceId, + }) + await db.insert(knowledgeConnectorSyncLog).values({ + id: ids.lockId, + connectorId: ids.connectorId, + status: 'started', + }) + retryDocumentId = generateId() + const old = new Date(Date.now() - 24 * 60 * 60_000) + await db.insert(document).values({ + id: retryDocumentId, + knowledgeBaseId: ids.knowledgeBaseId, + connectorId: ids.connectorId, + filename: 'Retained fixture.txt', + fileUrl: 'data:text/plain,fixture', + fileSize: 7, + mimeType: 'text/plain', + contentHash: 'fixture', + storageKey: 'fixture-retained', + processingStatus: 'failed', + processingAttempts: 1, + processingCompletedAt: old, + uploadedAt: old, + }) + }) + afterEach(async () => { + vi.restoreAllMocks() + 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])) + }) + afterAll(async () => { + await db.$client.end() + }) + + function run(action: Action) { + const result = emptyResult() + const lease = createContentSyncLease(ids.connectorId, ids.lockId) + if (action === 'complete') { + return completeSuccessfulSync( + ids.connectorId, + ids.knowledgeBaseId, + ids.lockId, + 60, + result, + null + ) + } + if (action === 'defer') { + return deferConnectorSync({ + connectorId: ids.connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + runId: ids.lockId, + lease, + kind: 'content', + result, + error: new GitHubRequestDeferredError(60_000), + }) + } + return sweepStuckDocuments({ + connectorId: ids.connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + syncStartedAt: new Date(), + retryCutoff: new Date(Date.now() - 7 * 24 * 60 * 60_000), + billingAttribution: billing, + result, + lease, + }) + } + + async function wrote(action: Action, tx: DbTransaction) { + if (action === 'recover') { + const [row] = await tx + .select({ status: document.processingStatus }) + .from(document) + .where(eq(document.id, retryDocumentId)) + return row.status === 'pending' + } + const [row] = await tx + .select({ status: knowledgeConnectorSyncLog.status }) + .from(knowledgeConnectorSyncLog) + .where(eq(knowledgeConnectorSyncLog.id, ids.lockId)) + return row.status !== 'started' + } + + function holdCommit(action: Action) { + const gate = { pid: undefined as number | undefined, release: deferred() } + const transaction = db.transaction.bind(db) + vi.spyOn(db, 'transaction').mockImplementation((callback, config) => + transaction(async (tx) => { + await tx.execute(sql`SET LOCAL statement_timeout = '10s'`) + await tx.execute(sql`SET LOCAL idle_in_transaction_session_timeout = '15s'`) + const result = await callback(tx) + if (gate.pid === undefined && (await wrote(action, tx))) { + const [backend] = await tx.execute<{ pid: number }>(sql`SELECT pg_backend_pid() AS pid`) + gate.pid = backend.pid + await gate.release.promise + } + return result + }, config) + ) + return gate + } + + it.each(ACTIONS)( + '%s can commit while an unrelated embedding holds the KB key-share lock', + async (action) => { + const release = deferred() + const acquired = deferred() + const holding = db.transaction(async (tx) => { + await tx.execute(sql`SET LOCAL idle_in_transaction_session_timeout = '15s'`) + await tx + .select({ id: knowledgeBase.id }) + .from(knowledgeBase) + .where(eq(knowledgeBase.id, ids.knowledgeBaseId)) + .for('key share') + acquired.resolve() + await release.promise + }) + await acquired.promise + const completed = Promise.allSettled([run(action)]) + let settled = false + void completed.then(() => { + settled = true + }) + try { + await expect.poll(() => settled, WAIT_OPTIONS).toBe(true) + expect(await completed).toMatchObject([{ status: 'fulfilled' }]) + } finally { + release.resolve() + await holding + await completed + } + } + ) + + it.each(ACTIONS)('%s keeps soft deletion excluded until its commit', async (action) => { + const gate = holdCommit(action) + const completed = Promise.allSettled([run(action)]) + let deleted: Promise[]> | undefined + try { + await expect.poll(() => gate.pid, WAIT_OPTIONS).toBeDefined() + deleted = Promise.allSettled([deleteKnowledgeBase(ids.knowledgeBaseId, 'fixture-delete')]) + await expect + .poll(async () => { + const [waiter] = await db.execute<{ pid: number }>(sql` + SELECT pid FROM pg_stat_activity WHERE datname = current_database() + AND wait_event_type = 'Lock' AND ${gate.pid!} = ANY(pg_blocking_pids(pid)) LIMIT 1 + `) + return waiter?.pid + }, WAIT_OPTIONS) + .toBeDefined() + const [active] = await db + .select({ deletedAt: knowledgeBase.deletedAt }) + .from(knowledgeBase) + .where(eq(knowledgeBase.id, ids.knowledgeBaseId)) + expect(active.deletedAt).toBeNull() + gate.release.resolve() + expect(await completed).toMatchObject([{ status: 'fulfilled' }]) + expect(await deleted).toMatchObject([{ status: 'fulfilled' }]) + const [archived] = await db + .select({ id: document.id }) + .from(document) + .where(and(eq(document.id, retryDocumentId), isNotNull(document.archivedAt))) + expect(archived?.id).toBe(retryDocumentId) + } finally { + gate.release.resolve() + await completed + await deleted + } + }) + + it.each(ACTIONS)('%s cannot commit writes from a replaced connector lease', async (action) => { + await db + .update(knowledgeConnector) + .set({ syncLockToken: generateId() }) + .where(eq(knowledgeConnector.id, ids.connectorId)) + await Promise.allSettled([run(action)]) + const [log] = await db + .select({ status: knowledgeConnectorSyncLog.status }) + .from(knowledgeConnectorSyncLog) + .where(eq(knowledgeConnectorSyncLog.id, ids.lockId)) + expect(log.status).toBe('started') + const [unchanged] = await db + .select({ status: document.processingStatus }) + .from(document) + .where(eq(document.id, retryDocumentId)) + expect(unchanged.status).toBe('failed') + }) +}) diff --git a/apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts b/apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts new file mode 100644 index 00000000000..ccb19da1da8 --- /dev/null +++ b/apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts @@ -0,0 +1,296 @@ +/** Real PostgreSQL coverage for resumable Google user work and atomic checkpoint advancement. */ +import { db } from '@sim/db' +import { + knowledgeConnector, + knowledgeConnectorGoogleUser, + organization, + user, + workspace, +} from '@sim/db/schema' +import { eq, inArray } from 'drizzle-orm' +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { seedKnowledgeAclFixture } from '@/lib/knowledge/__integration__/seed-source-access-fixture' +import { + commitGoogleCompanyWork, + googleCompanyWorkStore, +} from '@/lib/knowledge/connectors/google-company-store' +import { beginListingCheckpoint } from '@/lib/knowledge/connectors/listing-checkpoint' +import { assertSyncLeaseHeldInTx, stillHoldsSyncLock } from '@/lib/knowledge/connectors/sync-lock' + +describe('Google company user checkpoint storage', () => { + let owner: Awaited> + const generationId = 'google-fixture-generation' + const generationStartedAt = new Date(Date.now() - 13 * 60 * 60 * 1000) + const first = { + user: { id: 'first', email: 'first@fixture.test', customerId: 'customer' }, + cursor: 'saved-provider-page-91', + } + const second = { + user: { id: 'second', email: 'second@fixture.test', customerId: 'customer' }, + cursor: 'provider-first-page', + } + const checkpoint = beginListingCheckpoint({ + fingerprint: 'a'.repeat(64), + generationId, + startedAt: generationStartedAt, + }) + + beforeAll(async () => { + owner = await seedKnowledgeAclFixture(undefined, { connectorType: 'google_drive' }) + await db.transaction(async (tx) => { + await commitGoogleCompanyWork( + tx, + owner.connectorId, + generationId, + { enqueue: [first, second] }, + generationStartedAt + ) + await tx + .update(knowledgeConnector) + .set({ listingCheckpoint: checkpoint }) + .where(eq(knowledgeConnector.id, owner.connectorId)) + }) + }) + afterAll(async () => { + await db.delete(workspace).where(eq(workspace.id, owner.workspaceId)) + await db.delete(organization).where(eq(organization.id, owner.organizationId)) + await db.delete(user).where(inArray(user.id, [owner.aliceId, owner.bobId])) + await db.$client.end() + }) + + it('rolls back user progress and the owning checkpoint together', async () => { + const store = googleCompanyWorkStore(owner.connectorId, generationId) + const before = await store.get('first', 'content') + await expect( + db.transaction(async (tx) => { + await assertSyncLeaseHeldInTx(tx, owner.connectorId, { + stillHeld: () => stillHoldsSyncLock(owner.connectorId, owner.lockId), + }) + await commitGoogleCompanyWork( + tx, + owner.connectorId, + generationId, + { + update: { + userId: 'first', + kind: 'content', + cursor: 'next-page-92', + completed: false, + retryAt: new Date(), + attempts: 0, + failure: null, + }, + }, + generationStartedAt + ) + await tx + .update(knowledgeConnector) + .set({ listingCheckpoint: { ...checkpoint, cursor: 'outer-next-page' } }) + .where(eq(knowledgeConnector.id, owner.connectorId)) + throw new Error('simulated worker transaction failure') + }) + ).rejects.toThrow('simulated worker transaction failure') + expect(await store.get('first', 'content')).toEqual(before) + const [connector] = await db + .select({ checkpoint: knowledgeConnector.listingCheckpoint }) + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, owner.connectorId)) + expect(connector.checkpoint).toEqual(checkpoint) + }) + + it('pins provider replay, preserves it during repeated Directory discovery and refreshes old evidence promptly', async () => { + await db.transaction(async (tx) => { + await commitGoogleCompanyWork( + tx, + owner.connectorId, + generationId, + { pin: { userId: 'first', kind: 'content', cursor: 'stable-listing-snapshot' } }, + generationStartedAt + ) + await commitGoogleCompanyWork( + tx, + owner.connectorId, + generationId, + { enqueue: [{ ...first, cursor: 'must-not-overwrite' }] }, + generationStartedAt + ) + await commitGoogleCompanyWork( + tx, + owner.connectorId, + generationId, + { + update: { + userId: 'first', + kind: 'content', + cursor: 'next-content-page', + completed: false, + retryAt: new Date(), + attempts: 0, + failure: null, + }, + }, + generationStartedAt + ) + }) + const store = googleCompanyWorkStore(owner.connectorId, generationId) + expect((await store.get('first', 'content'))?.cursor).toBe('next-content-page') + expect((await store.next('content', new Date()))?.user.id).toBe('second') + expect((await store.next('permissions', new Date()))?.user.id).toBe('first') + expect(await store.get('first', 'permissions')).toMatchObject({ + cursor: undefined, + attempts: 0, + }) + }) + + it('retains blocked work independently of healthy completions and excludes another generation', async () => { + await db.transaction(async (tx) => { + await commitGoogleCompanyWork( + tx, + owner.connectorId, + generationId, + { + update: { + userId: 'first', + kind: 'content', + cursor: 'next-content-page', + completed: false, + retryAt: new Date(Date.now() + 60 * 60 * 1000), + attempts: 1, + failure: { + scope: first.user.email, + operation: 'drive.files.list', + status: 403, + reasons: [], + }, + }, + }, + generationStartedAt + ) + await commitGoogleCompanyWork( + tx, + owner.connectorId, + generationId, + { + update: { + userId: 'second', + kind: 'content', + cursor: null, + completed: true, + retryAt: new Date(Date.now() + 60 * 60 * 1000), + attempts: 0, + failure: null, + }, + }, + generationStartedAt + ) + await commitGoogleCompanyWork( + tx, + owner.connectorId, + 'different-generation', + { + enqueue: [{ user: { id: 'other', email: 'other@fixture.test', customerId: 'customer' } }], + }, + generationStartedAt + ) + }) + const store = googleCompanyWorkStore(owner.connectorId, generationId) + expect(await store.next('content', new Date())).toBeNull() + expect(await store.remaining()).toMatchObject({ + count: 1, + failures: { count: 1, samples: [{ scope: first.user.email, status: 403 }] }, + }) + expect((await store.get('first', 'content'))?.cursor).toBe('next-content-page') + expect(await store.get('other', 'content')).toBeNull() + expect(await googleCompanyWorkStore('other-connector', generationId).remaining()).toEqual({ + count: 0, + retryAt: null, + }) + }) + + it('does not rescan completed manual work or use its past retry time to wake blocked work', async () => { + await commitGoogleCompanyWork( + db, + owner.connectorId, + generationId, + { + update: { + userId: 'second', + kind: 'content', + cursor: null, + completed: true, + retryAt: new Date(Date.now() - 60_000), + attempts: 0, + failure: null, + }, + }, + generationStartedAt + ) + const automatic = googleCompanyWorkStore(owner.connectorId, generationId) + const manual = googleCompanyWorkStore(owner.connectorId, generationId, false) + expect((await automatic.next('content', new Date()))?.user.id).toBe('second') + expect(await manual.next('content', new Date())).toBeNull() + expect((await manual.remaining()).retryAt!.getTime()).toBeGreaterThan(Date.now()) + expect((await manual.next('permissions', new Date()))?.user.id).toBe('first') + await commitGoogleCompanyWork( + db, + owner.connectorId, + generationId, + { + update: { + userId: 'first', + kind: 'content', + cursor: null, + completed: true, + retryAt: new Date(Date.now() - 60_000), + attempts: 0, + failure: null, + }, + }, + generationStartedAt + ) + expect(await automatic.next('content', new Date())).toBeNull() + expect((await automatic.remaining()).count).toBe(0) + }) + + it('resets both provider continuations on a new configuration generation and cascades on connector deletion', async () => { + await db.transaction(async (tx) => { + await commitGoogleCompanyWork( + tx, + owner.connectorId, + generationId, + { + pin: { + userId: 'first', + kind: 'permissions', + cursor: 'old-config-permission-page', + permissionStartedAt: generationStartedAt, + }, + }, + generationStartedAt + ) + await commitGoogleCompanyWork( + tx, + owner.connectorId, + 'new-config', + { enqueue: [{ ...first, cursor: 'new-config-first-page' }] }, + new Date() + ) + }) + const store = googleCompanyWorkStore(owner.connectorId, 'new-config') + expect(await store.get('first', 'content')).toMatchObject({ + cursor: 'new-config-first-page', + attempts: 0, + }) + expect(await store.get('first', 'permissions')).toMatchObject({ + cursor: undefined, + permissionStartedAt: undefined, + }) + await db.delete(knowledgeConnector).where(eq(knowledgeConnector.id, owner.connectorId)) + expect( + await db + .select() + .from(knowledgeConnectorGoogleUser) + .where(eq(knowledgeConnectorGoogleUser.connectorId, owner.connectorId)) + ).toEqual([]) + }) +}) diff --git a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts index cf87bd54aa7..51c3efce155 100644 --- a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts @@ -22,7 +22,16 @@ import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' -const fixture = vi.hoisted(() => ({ storageRoot: '', list: vi.fn(), get: vi.fn() })) +const fixture = vi.hoisted(() => ({ + storageRoot: '', + list: vi.fn(), + get: vi.fn(), + directory: vi.fn(), +})) +vi.mock('@/connectors/google-workspace/users', async (original) => ({ + ...(await original()), + listGoogleWorkspaceUsers: fixture.directory, +})) vi.mock('@/lib/uploads/core/setup.server', () => ({ get UPLOAD_DIR_SERVER() { return fixture.storageRoot @@ -69,7 +78,10 @@ import { import { searchKnowledge } from '@/lib/knowledge/application/search' import * as connectorTokens from '@/lib/knowledge/connectors/access-token' import { getConnectorFailureDiagnostic } from '@/lib/knowledge/connectors/connector-error' -import { listingFingerprint } from '@/lib/knowledge/connectors/listing-checkpoint' +import { + beginListingCheckpoint, + listingFingerprint, +} from '@/lib/knowledge/connectors/listing-checkpoint' import * as memberAccess from '@/lib/knowledge/connectors/member-access' import { materializeDocumentAcls, @@ -83,7 +95,7 @@ import { runConnectorContentPass } from '@/lib/knowledge/connectors/sync-content import { executeSync } from '@/lib/knowledge/connectors/sync-engine' import { SOURCE_CONTENT_ERROR } from '@/lib/knowledge/connectors/sync-limits' import { createContentSyncLease, createMemberSyncLease } from '@/lib/knowledge/connectors/sync-lock' -import { addDocument } from '@/lib/knowledge/connectors/sync-persistence' +import { addDocument, persistDocumentAcls } from '@/lib/knowledge/connectors/sync-persistence' import * as documentService from '@/lib/knowledge/documents/service' import { downloadFileFromUrl } from '@/lib/uploads/utils/file-utils.server' import { CONNECTOR_REGISTRY } from '@/connectors/registry.server' @@ -150,6 +162,216 @@ describe('durable source and member cycles in PostgreSQL', () => { }) } + it('refreshes verified existing permissions while repairing changed bodies, without indexing discoveries or reconciling absence', async () => { + const connectorId = generateId() + const runId = generateId() + await db.insert(knowledgeConnector).values({ + id: connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + connectorType: 'google_drive', + status: 'syncing', + syncLockToken: runId, + accessMode: 'admin', + sourceConfig: {}, + }) + const oldVerifiedAt = new Date(Date.now() - 25 * 60 * 60 * 1000) + const ownerAcl = [`u:${ids.aliceId}@fixture.test`] + const stored = ['unchanged', 'changed', 'failed', 'absent'].map((id) => ({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + connectorId, + externalId: `refresh-${id}`, + filename: id, + fileUrl: '', + storageKey: `kb/refresh-old-${id}.txt`, + fileSize: 10, + mimeType: 'text/plain', + contentHash: id === 'unchanged' ? 'hash-refresh-unchanged' : 'old-body', + processingStatus: 'completed', + acl: ownerAcl, + aclVerifiedAt: oldVerifiedAt, + sourceSeenAt: oldVerifiedAt, + })) + await db.insert(document).values(stored) + const documents = ['unchanged', 'changed', 'failed', 'new'].map((id) => ({ + ...sourceDoc(`refresh-${id}`), + content: '', + contentDeferred: true, + acl: ownerAcl, + })) + const listDocuments = vi.fn(async () => ({ + documents, + hasMore: false, + permissionsOnly: true, + reconciliationSafe: false, + })) + const getDocument = vi.fn(async (externalId: string) => { + if (externalId === 'refresh-failed') throw new Error('User document unavailable') + const [duringHydration] = await db + .select({ acl: document.acl }) + .from(document) + .where(and(eq(document.connectorId, connectorId), eq(document.externalId, externalId))) + expect(duringHydration.acl).toEqual([]) + return sourceDoc(externalId) + }) + const [connector] = await db + .select() + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, connectorId)) + const syncResult = result() + const lease = createContentSyncLease(connectorId, runId) + const pass = await runConnectorContentPass({ + connectorId, + connector, + connectorConfig: { ...CONNECTOR_REGISTRY.google_drive, listDocuments }, + sourceConfig: {}, + syncContext: {}, + kbOwner: { userId: ids.aliceId, workspaceId: ids.workspaceId }, + billingAttribution: billing, + result: syncResult, + lease, + leaseKind: 'content', + runId, + fingerprint: listingFingerprint({ source: 'permission-refresh' }), + documentAccess: 'admin', + getAccessToken: async () => 'fixture', + hydration: { getDocument }, + forceRehydrate: false, + deadlineAt: Date.now() + 60_000, + onPage: async (verified) => { + await persistDocumentAcls( + connectorId, + new Map(verified.map((item) => [item.externalId, item.acl!])) + ) + return { permissionsIncomplete: false } + }, + }) + expect(getDocument.mock.calls.map(([id]) => id).sort()).toEqual([ + 'refresh-changed', + 'refresh-failed', + ]) + const rows = await db.select().from(document).where(eq(document.connectorId, connectorId)) + expect(rows).toHaveLength(4) + expect(rows.every((row) => row.deletedAt === null)).toBe(true) + const unchanged = rows.find((row) => row.externalId === 'refresh-unchanged')! + expect(unchanged.aclVerifiedAt!.getTime()).toBeGreaterThan(oldVerifiedAt.getTime()) + expect(unchanged.sourceSeenAt).toEqual(oldVerifiedAt) + const changed = rows.find((row) => row.externalId === 'refresh-changed')! + expect(changed.contentHash).toBe('hash-refresh-changed') + expect(changed.acl).toEqual(ownerAcl) + expect(changed.aclVerifiedAt!.getTime()).toBeGreaterThan(oldVerifiedAt.getTime()) + const failed = rows.find((row) => row.externalId === 'refresh-failed')! + expect(failed.acl).toEqual([]) + expect(failed.aclVerifiedAt).toBeNull() + expect(failed.processingStatus).toBe('failed') + expect(rows.find((row) => row.externalId === 'refresh-absent')!.aclVerifiedAt).toEqual( + oldVerifiedAt + ) + expect(pass.checkpoint.listedCount).toBe(0) + expect(syncResult).toMatchObject({ + docsAdded: 0, + docsUpdated: 1, + docsFailed: 1, + docsDeleted: 0, + }) + }) + + it('repairs a changed Google document already seen in an unfinished company generation', async () => { + const connectorId = generateId() + const runId = generateId() + const fingerprint = listingFingerprint({ source: 'google-content-rescan' }) + const checkpoint = beginListingCheckpoint({ + fingerprint, + generationId: 'rescan-generation', + startedAt: new Date(Date.now() - 13 * 60 * 60 * 1000), + }) + await db.insert(knowledgeConnector).values({ + id: connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + connectorType: 'google_drive', + status: 'syncing', + syncLockToken: runId, + accessMode: 'admin', + sourceConfig: {}, + listingCheckpoint: checkpoint, + }) + await db.insert(document).values({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + connectorId, + externalId: 'rescan-changed', + filename: 'Rescan', + fileUrl: '', + storageKey: 'kb/rescan-old.txt', + fileSize: 10, + mimeType: 'text/plain', + contentHash: 'old-body', + sourceSeenAt: new Date(checkpoint.startedAt), + processingStatus: 'completed', + acl: [], + }) + fixture.directory.mockResolvedValue({ + users: [ + { + id: 'google-user', + email: 'google-user@fixture.test', + customerId: 'customer', + active: true, + }, + ], + }) + const listed = { + ...sourceDoc('rescan-changed'), + content: '', + contentDeferred: true, + acl: ['u:google-user@fixture.test'], + } + const listDocuments = vi.fn(async () => ({ documents: [listed], hasMore: false })) + const getDocument = vi.fn(async () => sourceDoc('rescan-changed')) + const [connector] = await db + .select() + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, connectorId)) + const syncResult = result() + const pass = await runConnectorContentPass({ + connectorId, + connector, + connectorConfig: { ...CONNECTOR_REGISTRY.google_drive, listDocuments }, + sourceConfig: {}, + syncContext: { + mirrorsSourceAcls: true, + getDelegatedAccessToken: async () => 'fixture-user-token', + }, + kbOwner: { userId: ids.aliceId, workspaceId: ids.workspaceId }, + billingAttribution: billing, + result: syncResult, + lease: createContentSyncLease(connectorId, runId), + leaseKind: 'content', + runId, + fingerprint, + documentAccess: 'admin', + getAccessToken: async () => 'fixture-directory-token', + hydration: { getDocument }, + forceRehydrate: false, + deadlineAt: Date.now() + 60_000, + onPage: async (verified) => { + await persistDocumentAcls( + connectorId, + new Map(verified.map((item) => [item.externalId, item.acl!])) + ) + return { permissionsIncomplete: false } + }, + }) + expect(pass.complete).toBe(true) + expect(getDocument).toHaveBeenCalledOnce() + expect(syncResult.docsUpdated).toBe(1) + const [stored] = await db.select().from(document).where(eq(document.connectorId, connectorId)) + expect(stored.contentHash).toBe('hash-rescan-changed') + expect(stored.aclVerifiedAt!.getTime()).toBeGreaterThan( + new Date(checkpoint.startedAt).getTime() + ) + }) + it('reconciles NULL and microsecond timestamp ties across ACL, soft-delete, and hard-delete pages', async () => { const connectorId = generateId() let runId = generateId() diff --git a/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts b/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts index 80c4fcb7172..4bff77a42e6 100644 --- a/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts @@ -6,6 +6,7 @@ import path from 'node:path' import { db } from '@sim/db' import { document, + embedding, knowledgeBase, knowledgeConnector, member, @@ -340,8 +341,8 @@ describe('independent recovery of retained connector documents', () => { return resolve(id) }) try { - expect(await recoverKnowledgeDocumentProcessing()).toBe(0) expect(await recoverKnowledgeDocumentProcessing()).toBe(1) + expect(await recoverKnowledgeDocumentProcessing()).toBe(0) } finally { spy.mockRestore() } @@ -384,6 +385,89 @@ describe('independent recovery of retained connector documents', () => { } ) + it('recovers another document while an index transaction holds the same KB foreign-key lock', async () => { + const ids = await seed() + const file = await failedFile(ids) + const indexedId = generateId() + await db.insert(document).values({ + id: indexedId, + knowledgeBaseId: ids.knowledgeBaseId, + filename: 'Concurrent index.txt', + fileUrl: 'data:text/plain,fixture', + fileSize: 7, + mimeType: 'text/plain', + processingStatus: 'completed', + }) + let release!: () => void + let acquired!: () => void + const released = new Promise((resolve) => { + release = resolve + }) + const locked = new Promise((resolve) => { + acquired = resolve + }) + const indexing = db.transaction(async (tx) => { + await tx.insert(embedding).values({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + documentId: indexedId, + chunkIndex: 0, + chunkHash: 'fixture', + content: 'fixture', + contentLength: 7, + tokenCount: 1, + startOffset: 0, + endOffset: 7, + embedding: [1, ...Array(1535).fill(0)], + }) + acquired() + await released + }) + await locked + try { + expect(await recoverKnowledgeDocumentProcessing()).toBe(1) + expect(await eventsFor(ids)).toHaveLength(1) + const [admitted] = await db.select().from(document).where(eq(document.id, file.documentId)) + expect(admitted.processingAttempts).toBe(2) + } finally { + release() + await indexing + } + }) + + it('does not claim work while a KB soft deletion is committing', async () => { + const ids = await seed() + const file = await failedFile(ids) + let release!: () => void + let acquired!: () => void + const released = new Promise((resolve) => { + release = resolve + }) + const locked = new Promise((resolve) => { + acquired = resolve + }) + const deletion = db.transaction(async (tx) => { + await tx + .update(knowledgeBase) + .set({ deletedAt: new Date() }) + .where(eq(knowledgeBase.id, ids.knowledgeBaseId)) + acquired() + await released + }) + await locked + try { + expect(await recoverKnowledgeDocumentProcessing()).toBe(0) + } finally { + release() + await deletion + } + expect(await recoverKnowledgeDocumentProcessing()).toBe(0) + expect(await eventsFor(ids)).toHaveLength(0) + const [original] = await db.select().from(document).where(eq(document.id, file.documentId)) + expect(original.processingAttempts).toBe(1) + expect(original.processingQueueToken).toBe('old-fixture-generation') + }) + it('replays the additive migration and leaves the concurrent recovery index valid', async () => { const migration = readFileSync( path.resolve( diff --git a/apps/sim/lib/knowledge/application/connectors.ts b/apps/sim/lib/knowledge/application/connectors.ts index dee4bb40742..6f809bdde88 100644 --- a/apps/sim/lib/knowledge/application/connectors.ts +++ b/apps/sim/lib/knowledge/application/connectors.ts @@ -370,8 +370,10 @@ async function resolveConnectorValidationAccessToken( if (!resolved || !getDelegatedAccessToken) return resolved return { ...resolved, - getDelegatedAccessToken: (subject) => - getDelegatedAccessToken(subject).catch(rethrowConnectorCredentialError), + getDelegatedAccessToken: (subject, signal) => + (signal ? getDelegatedAccessToken(subject, signal) : getDelegatedAccessToken(subject)).catch( + rethrowConnectorCredentialError + ), } } diff --git a/apps/sim/lib/knowledge/connectors/access-token.test.ts b/apps/sim/lib/knowledge/connectors/access-token.test.ts index 4fac4d3b56c..488c5ae82ae 100644 --- a/apps/sim/lib/knowledge/connectors/access-token.test.ts +++ b/apps/sim/lib/knowledge/connectors/access-token.test.ts @@ -343,6 +343,44 @@ describe('delegated connector access', () => { expect(JSON.stringify(syncContextForToken(token!))).toBe('{}') }) + it('forwards cancellation to both the directory token and delegated user token', async () => { + const controller = new AbortController() + const token = await resolveConnectorAccessToken({ + auth: delegationAuth, + accessMode: 'admin', + connector: credentialConnector('service-credential'), + userId: 'actor', + requestId: 'request', + sourceConfig: { adminEmail: 'admin@example.com' }, + signal: controller.signal, + }) + expect(mockResolveTokenBundle).toHaveBeenCalledWith( + 'service-credential', + 'actor', + 'request', + delegationAuth.adminServiceAccountScopes, + 'admin@example.com', + { signal: controller.signal } + ) + await token?.getDelegatedAccessToken?.('employee@example.com') + expect(mockGetServiceAccountToken).toHaveBeenCalledWith( + 'service-credential', + [driveScope], + 'employee@example.com', + { signal: controller.signal } + ) + const child = new AbortController() + await token?.getDelegatedAccessToken?.('second@example.com', child.signal) + const combined = mockGetServiceAccountToken.mock.lastCall?.[3].signal as AbortSignal + expect(combined.aborted).toBe(false) + child.abort() + expect(combined.aborted).toBe(true) + const reason = new DOMException('Caller cancelled', 'AbortError') + controller.abort(reason) + await expect(token?.getDelegatedAccessToken?.('employee@example.com')).rejects.toBe(reason) + expect(mockGetServiceAccountToken).toHaveBeenCalledTimes(2) + }) + it.each([ null, { credentialType: 'oauth', credentialId: 'service-credential', providerId: 'google-drive' }, diff --git a/apps/sim/lib/knowledge/connectors/access-token.ts b/apps/sim/lib/knowledge/connectors/access-token.ts index 82b4996fab3..219303a9321 100644 --- a/apps/sim/lib/knowledge/connectors/access-token.ts +++ b/apps/sim/lib/knowledge/connectors/access-token.ts @@ -30,7 +30,7 @@ export interface ConnectorAccessToken { /** The trusted site domain belonging to the credential's cloud id. */ domain?: string /** Server-only capability bound to this credential and the connector's declared scopes. */ - getDelegatedAccessToken?: (subject: string) => Promise + getDelegatedAccessToken?: (subject: string, signal?: AbortSignal) => Promise } /** @@ -97,8 +97,10 @@ export async function resolveConnectorAccessToken(params: { * person's files. */ sourceConfig: Record + signal?: AbortSignal }): Promise { const { auth, connector, userId, requestId } = params + params.signal?.throwIfAborted() const apiKeyConfig = getConnectorApiKeyConfig(auth) if ( @@ -137,7 +139,14 @@ export async function resolveConnectorAccessToken(params: { requestId, connectorServiceAccountScopes(auth, params.accessMode), subject, - ...(githubRepositoryScope ? [{ githubRepositoryScope }] : []) + ...(githubRepositoryScope || params.signal + ? [ + { + ...(githubRepositoryScope ? { githubRepositoryScope } : {}), + ...(params.signal ? { signal: params.signal } : {}), + }, + ] + : []) ) if (!bundle?.accessToken) return null @@ -155,12 +164,22 @@ export async function resolveConnectorAccessToken(params: { ) { const credentialId = identity.credentialId const scopes = [...auth.serviceAccountDelegationScopes] - getDelegatedAccessToken = async (subject) => { + getDelegatedAccessToken = async (subject, signal) => { + const tokenSignal = + signal && params.signal + ? AbortSignal.any([signal, params.signal]) + : (signal ?? params.signal) + tokenSignal?.throwIfAborted() const email = normalizeEmail(subject) if (!isValidEmailSyntax(email)) { throw new Error('A valid Workspace user email is required for delegated access') } - return getServiceAccountToken(credentialId, scopes, email) + return getServiceAccountToken( + credentialId, + scopes, + email, + ...(tokenSignal ? [{ signal: tokenSignal }] : []) + ) } } } diff --git a/apps/sim/lib/knowledge/connectors/connector-error.test.ts b/apps/sim/lib/knowledge/connectors/connector-error.test.ts index 69a995c95e6..a94305e22b0 100644 --- a/apps/sim/lib/knowledge/connectors/connector-error.test.ts +++ b/apps/sim/lib/knowledge/connectors/connector-error.test.ts @@ -2,7 +2,10 @@ 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 { + GoogleDriveApiError, + readGoogleDriveApiError, +} from '@/connectors/google-drive/google-drive-errors' import { ConnectorDirectoryError } from '@/connectors/source-error' describe('connector failure diagnostics', () => { @@ -114,6 +117,24 @@ describe('connector failure diagnostics', () => { }) }) + it('preserves safe reason completeness through a wrapped Directory error', async () => { + const cause = await readGoogleDriveApiError( + Response.json({ error: { errors: [{ reason: 'private-unknown-reason' }] } }, { status: 403 }), + 'directory.members.list' + ) + const diagnostic = getConnectorFailureDiagnostic( + new ConnectorDirectoryError('private group', { cause }) + ) + expect(diagnostic).toMatchObject({ + phase: 'directory', + status: 403, + operation: 'directory.members.list', + reasons: [], + reasonState: 'filtered', + }) + expect(JSON.stringify(diagnostic)).not.toContain('private') + }) + 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 737903da280..e82d9715f2c 100644 --- a/apps/sim/lib/knowledge/connectors/connector-error.ts +++ b/apps/sim/lib/knowledge/connectors/connector-error.ts @@ -4,6 +4,7 @@ import { ConnectorDirectoryError, ConnectorSourceError, type ConnectorSourceFailureCategory, + type ConnectorSourceReasonState, } from '@/connectors/source-error' export interface ConnectorFailureDiagnostic { @@ -13,6 +14,7 @@ export interface ConnectorFailureDiagnostic { code?: string operation?: string reasons?: readonly string[] + reasonState?: ConnectorSourceReasonState phase?: 'directory' } diff --git a/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts b/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts new file mode 100644 index 00000000000..256611f5b73 --- /dev/null +++ b/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts @@ -0,0 +1,445 @@ +/** @vitest-environment node */ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { + createGoogleCompanyScheduler, + type GoogleCompanyWorkChanges, + type GoogleCompanyWorkItem, + type GoogleCompanyWorkKind, + type GoogleCompanyWorkStore, +} from '@/lib/knowledge/connectors/google-company-scheduler' +import { + beginListingCheckpoint, + type ListingCheckpoint, + runResumableListing, +} from '@/lib/knowledge/connectors/listing-checkpoint' +import { GoogleDriveApiError } from '@/connectors/google-drive/google-drive-errors' +import { GoogleApiError } from '@/connectors/google-workspace/api-errors' +import type { GoogleWorkspaceUser } from '@/connectors/google-workspace/users' +import { ConnectorSourceError } from '@/connectors/source-error' +import type { ConnectorConfig, ExternalDocument, ExternalListingFailures } from '@/connectors/types' + +const mocks = vi.hoisted(() => ({ directory: vi.fn() })) +vi.mock('@/connectors/google-workspace/users', async (original) => ({ + ...(await original()), + listGoogleWorkspaceUsers: mocks.directory, +})) + +const document: ExternalDocument = { + externalId: 'doc', + title: 'Document', + content: 'Text', + contentHash: 'hash', + mimeType: 'text/plain', +} +function user(id: string): GoogleWorkspaceUser { + return { id, email: `${id}@fixture.test`, customerId: 'customer', active: true } +} +interface FakeWork extends GoogleCompanyWorkItem { + complete: boolean + retryAt: Date + served: number + failure?: ExternalListingFailures['samples'][number] +} +const expiredCursor = new Error('expired provider cursor') +function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { + let clock = new Date('2026-09-17T00:00:00Z') + let served = 0 + const rows = new Map() + const key = (id: string, kind: GoogleCompanyWorkKind) => `${id}:${kind}` + const store: GoogleCompanyWorkStore = { + get: async (id, kind) => rows.get(key(id, kind)) ?? null, + next: async (kind, now) => + [...rows.values()] + .filter( + (row) => + row.kind === kind && + row.retryAt <= now && + (kind !== 'content' || + !row.complete || + (syncIntervalMinutes > 0 && + [...rows.values()].some((item) => item.kind === 'content' && !item.complete))) && + (kind === 'content' || (rows.get(key(row.user.id, 'content'))?.served ?? 0) > 0) + ) + .sort((a, b) => a.served - b.served || a.user.id.localeCompare(b.user.id))[0] ?? null, + remaining: async () => { + const remaining = [...rows.values()].filter((row) => row.kind === 'content' && !row.complete) + const failed = [...rows.values()].filter((row) => row.failure) + return { + count: remaining.length, + retryAt: remaining.sort((a, b) => +a.retryAt - +b.retryAt)[0]?.retryAt ?? null, + ...(failed.length + ? { + failures: { + count: failed.length, + samples: failed.map((row) => row.failure!).slice(0, 10), + }, + } + : {}), + } + }, + } + const apply = (changes: GoogleCompanyWorkChanges) => { + for (const item of changes.enqueue ?? []) { + if (rows.has(key(item.user.id, 'content'))) continue + rows.set(key(item.user.id, 'content'), { + ...item, + kind: 'content', + attempts: 0, + hasFailure: false, + complete: false, + retryAt: clock, + served: 0, + }) + rows.set(key(item.user.id, 'permissions'), { + user: item.user, + kind: 'permissions', + attempts: 0, + hasFailure: false, + complete: false, + retryAt: new Date(+clock + 12 * 60 * 60 * 1000), + served: 0, + }) + } + if (changes.pin) + Object.assign(rows.get(key(changes.pin.userId, changes.pin.kind))!, { + cursor: changes.pin.cursor, + permissionStartedAt: changes.pin.permissionStartedAt, + }) + if (changes.update) { + const change = changes.update + Object.assign(rows.get(key(change.userId, change.kind))!, { + cursor: change.cursor ?? undefined, + complete: change.kind === 'content' && change.completed, + retryAt: change.retryAt, + attempts: change.attempts, + failure: change.failure ?? undefined, + served: ++served, + permissionStartedAt: change.permissionStartedAt ?? undefined, + }) + for (const kind of ['content', 'permissions'] as const) { + rows.get(key(change.userId, kind))!.hasFailure = Boolean( + rows.get(key(change.userId, 'content'))?.failure || + rows.get(key(change.userId, 'permissions'))?.failure + ) + } + } + } + const list = vi.fn(async () => ({ + documents: [document], + hasMore: false, + })) + let scheduler = createGoogleCompanyScheduler({ + provider, + store, + listDocuments: list, + syncIntervalMinutes, + isListingCursorInvalidError: (error) => error === expiredCursor, + now: () => clock, + }) + let saved = beginListingCheckpoint({ + fingerprint: 'a'.repeat(64), + generationId: 'generation', + startedAt: clock, + }) + const process = vi.fn(async () => undefined) + const step = async (maxPages = 1) => { + saved = await runResumableListing({ + connectorConfig: { + listDocuments: scheduler.listDocuments, + isListingCursorInvalidError: (error) => error === expiredCursor, + }, + sourceConfig: {}, + syncContext: {}, + checkpoint: saved, + deadlineAt: Date.now() + 60_000, + maxPages, + beforePage: async () => undefined, + getAccessToken: async () => 'directory-token', + processPage: process, + saveCheckpoint: async (checkpoint) => { + const changes = scheduler.changesFor(checkpoint.cursor) + if (changes) apply(changes) + saved = structuredClone(checkpoint) + scheduler.didCommit(checkpoint.cursor) + }, + }) + return saved + } + return { + rows, + list, + process, + step, + saved: () => saved, + setSaved: (checkpoint: ListingCheckpoint) => { + saved = checkpoint + }, + advance: (ms: number) => { + clock = new Date(+clock + ms) + }, + restart: () => { + scheduler = createGoogleCompanyScheduler({ + provider, + store, + listDocuments: list, + syncIntervalMinutes, + isListingCursorInvalidError: (error) => error === expiredCursor, + now: () => clock, + }) + }, + } +} + +beforeEach(() => { + mocks.directory.mockReset() +}) + +describe('durable Google company user scheduling', () => { + it('reaches later Directory pages before draining a large first mailbox, then rotates its exact cursor', async () => { + mocks.directory + .mockResolvedValueOnce({ users: [user('a'), user('b')], nextPageToken: 'directory-2' }) + .mockResolvedValueOnce({ users: [user('z')] }) + const f = fixture() + f.list.mockImplementation(async (_token, _config, cursor) => { + if (cursor?.includes('large-page')) return { documents: [], hasMore: false } + return { documents: [document], hasMore: true, nextCursor: 'large-page-2' } + }) + await f.step(6) + expect(mocks.directory).toHaveBeenCalledTimes(2) + expect(f.rows.get('z:content')?.served).toBeGreaterThan(0) + expect(f.list.mock.calls.some((call) => call[2] === 'large-page-2')).toBe(true) + expect(f.saved().cursor!.length).toBeLessThan(2048) + }) + + it.each(['google_calendar', 'google_drive'])( + 'retains an unresolved %s user and continues other users', + async (provider) => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('z')] }) + const f = fixture(provider) + f.list.mockRejectedValueOnce( + provider === 'google_drive' + ? new GoogleDriveApiError(403, [], 'drive.files.list', false) + : new GoogleApiError('calendar.events.list', 403, [], false) + ) + await f.step(4) + expect(f.rows.get('a:content')).toMatchObject({ + complete: false, + attempts: 1, + failure: { status: 403, reasons: [] }, + }) + expect(f.rows.get('z:content')?.complete).toBe(true) + expect(f.saved()).toMatchObject({ + complete: false, + unsafe: true, + listingFailures: { count: 1 }, + }) + expect(f.saved().resumeAt).not.toBeNull() + f.advance(10 * 60 * 1000) + f.restart() + await f.step(3) + expect(f.rows.get('a:content')?.complete).toBe(true) + } + ) + + it.each(['insufficientPermissions', 'rateLimitExceeded', 'SERVICE_DISABLED'])( + 'keeps %s as a connector-level failure', + async (reason) => { + mocks.directory.mockResolvedValue({ users: [user('a')] }) + const f = fixture() + const error = new GoogleApiError('calendar.events.list', 403, [reason]) + f.list.mockRejectedValue(error) + await f.step() + await expect(f.step()).rejects.toBe(error) + expect(f.rows.get('a:content')?.attempts).toBe(0) + } + ) + + it('bounds a run of unresolved user errors rather than marking the tenant complete', async () => { + mocks.directory.mockResolvedValue({ users: ['a', 'b', 'c', 'd'].map(user) }) + const f = fixture() + f.list.mockRejectedValue(new GoogleApiError('calendar.events.list', 403, [], false)) + await f.step(25) + expect(f.list).toHaveBeenCalledTimes(3) + expect(f.saved()).toMatchObject({ complete: false, unsafe: true }) + expect(f.rows.get('d:content')?.served).toBe(0) + expect(f.saved().resumeAt).toBe('2026-09-17T00:05:00.000Z') + }) + + it('adopts a production cursor without resetting the active provider page', async () => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('z')] }) + const f = fixture() + const legacy = `google-workspace:v1:${Buffer.from(JSON.stringify({ provider: 'google_calendar', users: [user('a')], providerCursor: 'existing-page-91', nextUsersPageToken: 'old-directory' })).toString('base64url')}` + f.setSaved({ ...f.saved(), cursor: legacy, listedCount: 6224 }) + await f.step(3) + expect(f.list.mock.calls[0]?.[2]).toContain('google-workspace:v1:') + const resume = JSON.parse( + Buffer.from(f.list.mock.calls[0]![2]!.split(':v1:')[1], 'base64url').toString() + ) + expect(resume.providerCursor).toBe('existing-page-91') + expect(f.saved().listedCount).toBe(6225) + expect(mocks.directory.mock.calls[0]?.[1]).toBeUndefined() + }) + + it('pins the provider snapshot before processing and replays it after interruption', async () => { + mocks.directory.mockResolvedValue({ users: [user('a')] }) + const f = fixture() + f.list.mockResolvedValue({ + documents: [document], + currentCursor: 'stable-provider-snapshot', + hasMore: true, + nextCursor: 'next-provider-page', + }) + await f.step() + f.process.mockRejectedValueOnce(new Error('interrupted')) + await expect(f.step()).rejects.toThrow('interrupted') + expect(f.rows.get('a:content')?.cursor).toBe('stable-provider-snapshot') + expect(f.saved().listedCount).toBe(0) + f.restart() + await f.step() + expect(f.list.mock.calls.at(-1)?.[2]).toBe('stable-provider-snapshot') + expect(f.rows.get('a:content')?.cursor).toBe('next-provider-page') + }) + + it('refreshes permissions with a separate cursor while content remains unfinished', async () => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('z')] }) + const f = fixture() + f.list.mockResolvedValue({ documents: [document], hasMore: true, nextCursor: 'next-page' }) + await f.step(2) + const contentCursor = f.rows.get('a:content')?.cursor + f.advance(13 * 60 * 60 * 1000) + await f.step(2) + expect(f.process.mock.calls.at(-1)?.[2]).toMatchObject({ permissionsOnly: true }) + expect(f.rows.get('a:content')?.cursor).toBe(contentCursor) + expect(f.rows.get('a:permissions')?.cursor).toBe('next-page') + expect(f.saved().listedCount).toBe(1) + await f.step() + expect(f.rows.get('z:content')?.served).toBeGreaterThan(0) + }) + + it('restarts expired Directory discovery without losing committed user work', async () => { + mocks.directory + .mockResolvedValueOnce({ users: [user('a')], nextPageToken: 'expired' }) + .mockRejectedValueOnce(new ConnectorSourceError('expired', 400)) + .mockResolvedValueOnce({ users: [user('a'), user('z')] }) + const f = fixture() + f.list.mockResolvedValue({ + documents: [document], + hasMore: true, + nextCursor: 'saved-user-page', + }) + await f.step(4) + expect(f.rows.get('a:content')?.cursor).toBe('saved-user-page') + expect(f.rows.has('z:content')).toBe(true) + }) + + it('discovers new employees and rescans healthy users while an unavailable mailbox stays blocked', async () => { + mocks.directory + .mockResolvedValueOnce({ users: [user('a'), user('z')] }) + .mockResolvedValue({ users: [user('a'), user('new'), user('z')] }) + const f = fixture() + f.list.mockImplementation(async (_token, _config, cursor) => { + const state = JSON.parse(Buffer.from(cursor!.split(':v1:')[1], 'base64url').toString()) + if (state.users[0].id === 'a') + return { + documents: [], + hasMore: false, + listingFailures: { + count: 1, + samples: [ + { + scope: 'a@fixture.test', + operation: 'directory.users.get', + reasons: ['mailboxNotSetup'], + }, + ], + }, + } + return { documents: [{ ...document, externalId: state.users[0].id }], hasMore: false } + }) + await f.step(4) + const oldCount = f.saved().listedCount + expect(f.rows.get('a:content')?.complete).toBe(false) + expect(f.rows.get('z:content')?.complete).toBe(true) + f.advance(61 * 60_000) + f.restart() + await f.step(5) + expect(f.rows.get('new:content')?.complete).toBe(true) + expect(f.rows.get('z:content')?.complete).toBe(true) + expect(f.saved().listedCount).toBe(oldCount + 2) + expect(f.saved().complete).toBe(false) + }) + + it('rejects an oversized continuation before making provider requests', async () => { + const f = fixture() + f.setSaved({ ...f.saved(), cursor: `google-company-work:v2:${'a'.repeat(20_000)}` }) + await expect(f.step()).rejects.toThrow('too large') + expect(mocks.directory).not.toHaveBeenCalled() + expect(f.list).not.toHaveBeenCalled() + }) + + it.each(['content', 'permissions'] as const)( + 'resets only the expired %s cursor, including interruption before the recovery commit', + async (kind) => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('b')] }) + const f = fixture('google_drive') + await f.step() + const a = f.rows.get(`a:${kind}`)! + a.cursor = 'expired-inner-cursor' + a.retryAt = new Date('2026-09-17T00:00:00Z') + a.permissionStartedAt = new Date('2026-09-16T00:00:00Z') + if (kind === 'permissions') { + const content = f.rows.get('a:content')! + content.served = 1 + content.retryAt = new Date('2026-09-18T00:00:00Z') + } + const b = f.rows.get('b:content')! + b.cursor = 'b-content-page-42' + b.retryAt = new Date('2026-09-18T00:00:00Z') + f.rows.get('b:permissions')!.cursor = 'b-permission-page-7' + const beforeB = structuredClone(b) + f.list.mockRejectedValue(expiredCursor) + f.process.mockRejectedValueOnce(new Error('interrupted before recovery commit')) + await expect(f.step()).rejects.toThrow('interrupted before recovery commit') + expect(a.cursor).toBe('expired-inner-cursor') + expect(f.saved().generationId).toBe('generation') + f.restart() + await f.step(3) + expect(a.cursor).toContain('gdrive-company:v1:') + expect(a.complete).toBe(false) + expect(a.attempts).toBe(1) + expect(a.retryAt).toEqual(new Date('2026-09-17T01:00:00Z')) + if (kind === 'permissions') expect(a.permissionStartedAt).toBeUndefined() + expect(f.rows.get('b:content')).toEqual(beforeB) + expect(f.rows.get('b:permissions')?.cursor).toBe('b-permission-page-7') + expect(f.saved()).toMatchObject({ + generationId: 'generation', + listedCount: 0, + complete: false, + }) + expect(f.list).toHaveBeenCalledTimes(2) + } + ) + + it('schedules completed users at the supplied refresh cadence', async () => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('b')] }) + const f = fixture('gmail', 15) + await f.step(2) + expect(f.rows.get('a:content')?.retryAt).toEqual(new Date('2026-09-17T00:15:00Z')) + }) + + it('keeps completed manual users complete while unfinished work and permission refresh continue', async () => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('b')] }) + const f = fixture('gmail', 0) + await f.step(2) + const a = f.rows.get('a:content')! + const before = structuredClone(a) + const b = f.rows.get('b:content')! + b.retryAt = new Date('2026-09-18T00:00:00Z') + f.advance(13 * 60 * 60 * 1000) + await f.step(2) + expect(f.rows.get('a:content')).toEqual(before) + expect(f.process.mock.calls.at(-2)?.[2]).toMatchObject({ permissionsOnly: true }) + expect(mocks.directory).toHaveBeenCalledTimes(1) + expect(f.saved().resumeAt).toBe('2026-09-18T00:00:00.000Z') + }) +}) diff --git a/apps/sim/lib/knowledge/connectors/google-company-scheduler.ts b/apps/sim/lib/knowledge/connectors/google-company-scheduler.ts new file mode 100644 index 00000000000..aeae5ba99c1 --- /dev/null +++ b/apps/sim/lib/knowledge/connectors/google-company-scheduler.ts @@ -0,0 +1,410 @@ +import { z } from 'zod' +import { googleDriveCompanyCursorAdapter } from '@/connectors/google-drive/company-crawl' +import { GoogleDriveApiError } from '@/connectors/google-drive/google-drive-errors' +import { GoogleApiError } from '@/connectors/google-workspace/api-errors' +import { googleWorkspaceCompanyCursorAdapter } from '@/connectors/google-workspace/company-crawl' +import type { + GoogleCompanyCursorAdapter, + GoogleCompanyUserWork, +} from '@/connectors/google-workspace/company-work' +import { + listGoogleWorkspaceUsers, + selectedGoogleWorkspaceUsers, +} from '@/connectors/google-workspace/users' +import { MAX_LISTING_FAILURE_SAMPLES } from '@/connectors/listing-failures' +import { ConnectorSourceError } from '@/connectors/source-error' +import type { + ConnectorConfig, + ExternalDocumentList, + ExternalListingFailures, +} from '@/connectors/types' + +const CURSOR_PREFIX = 'google-company-work:v2:' +const PERMISSION_REFRESH_MS = 12 * 60 * 60 * 1000 +const DIRECTORY_REFRESH_MS = 60 * 60 * 1000 +const MAX_UNRESOLVED_FAILURES_PER_PASS = 3 +const cursorSchema = z.object({ + directoryComplete: z.boolean(), + directoryCursor: z.string().max(8192).optional(), + directoryRefreshAt: z.string().datetime().optional(), + phase: z.enum(['directory', 'users']), + revision: z.number().int().nonnegative(), + permissionTurn: z.boolean(), + active: z + .object({ userId: z.string().min(1).max(256), kind: z.enum(['content', 'permissions']) }) + .optional(), +}) +type CompanyWorkCursor = z.infer +export type GoogleCompanyWorkKind = 'content' | 'permissions' +type ListingFailure = ExternalListingFailures['samples'][number] + +export interface GoogleCompanyWorkItem extends GoogleCompanyUserWork { + kind: GoogleCompanyWorkKind + attempts: number + hasFailure: boolean + permissionStartedAt?: Date +} + +export interface GoogleCompanyWorkUpdate { + userId: string + kind: GoogleCompanyWorkKind + cursor: string | null + completed: boolean + retryAt: Date + attempts: number + failure: ListingFailure | null + permissionStartedAt?: Date | null +} + +/** All writes are supplied to the caller's lease-guarded checkpoint transaction. */ +export interface GoogleCompanyWorkChanges { + enqueue?: GoogleCompanyUserWork[] + pin?: { userId: string; kind: GoogleCompanyWorkKind; cursor: string; permissionStartedAt?: Date } + update?: GoogleCompanyWorkUpdate +} + +export interface GoogleCompanyWorkStore { + get: (userId: string, kind: GoogleCompanyWorkKind) => Promise + next: (kind: GoogleCompanyWorkKind, now: Date) => Promise + remaining: () => Promise<{ + count: number + retryAt: Date | null + failures?: ExternalListingFailures + }> +} + +function adapterFor(provider: string): GoogleCompanyCursorAdapter | null { + if (provider === 'google_drive') return googleDriveCompanyCursorAdapter + if (provider === 'gmail' || provider === 'google_calendar') + return googleWorkspaceCompanyCursorAdapter(provider) + return null +} + +export function isGoogleCompanySource(provider: string, context: Record): boolean { + return ( + adapterFor(provider) !== null && + context.mirrorsSourceAcls === true && + typeof context.getDelegatedAccessToken === 'function' + ) +} + +function writeCursor(state: CompanyWorkCursor): string { + return `${CURSOR_PREFIX}${Buffer.from(JSON.stringify(state)).toString('base64url')}` +} + +function readCursor(cursor: string): CompanyWorkCursor { + if (cursor.length > 16 * 1024) throw new Error('Google company work cursor is too large') + return cursorSchema.parse( + JSON.parse(Buffer.from(cursor.slice(CURSOR_PREFIX.length), 'base64url').toString('utf8')) + ) +} + +/** An unresolved per-user 403 remains retryable partial coverage, never proof that access was revoked. */ +function deferredUserFailure( + error: unknown, + user: GoogleCompanyUserWork['user'] +): ListingFailure | null { + if ( + (!(error instanceof GoogleApiError) && !(error instanceof GoogleDriveApiError)) || + error.status !== 403 || + !error.diagnostic + ) + return null + const { operation, reasons } = error.diagnostic + const userListing = + operation === 'calendar.events.list' || + operation === 'gmail.threads.list' || + operation === 'drive.files.list' || + operation === 'drive.drives.list' + const globalReasons = new Set([ + 'accessNotConfigured', + 'authError', + 'insufficientPermissions', + 'domainPolicy', + 'serviceDisabled', + 'ACCESS_TOKEN_SCOPE_INSUFFICIENT', + 'API_KEY_SERVICE_BLOCKED', + 'SERVICE_DISABLED', + 'USER_PROJECT_DENIED', + 'dailyLimitExceeded', + 'quotaExceeded', + ]) + if (!userListing || error.rateLimited || reasons.some((reason) => globalReasons.has(reason))) + return null + return { + scope: user.email, + operation, + status: error.status, + reasons: [...reasons], + ...(error.diagnostic.reasonState ? { reasonState: error.diagnostic.reasonState } : {}), + } +} + +/** Durable fair scheduling adds no parallel provider or database work. */ +export function createGoogleCompanyScheduler(input: { + provider: string + store: GoogleCompanyWorkStore + listDocuments: ConnectorConfig['listDocuments'] + isListingCursorInvalidError?: ConnectorConfig['isListingCursorInvalidError'] + syncIntervalMinutes: number + now?: () => Date +}) { + const adapter = adapterFor(input.provider) + if (!adapter) throw new Error('Unsupported Google company provider') + const now = input.now ?? (() => new Date()) + const changes = new Map() + let unresolvedFailures = 0 + let permissionStartedAt: Date | undefined + const initial = (): CompanyWorkCursor => ({ + directoryComplete: false, + phase: 'directory', + revision: 0, + permissionTurn: false, + }) + const nextCursor = (state: CompanyWorkCursor, change: GoogleCompanyWorkChanges): string => { + const cursor = writeCursor({ ...state, active: undefined, revision: state.revision + 1 }) + changes.set(cursor, change) + return cursor + } + const listDocuments: ConnectorConfig['listDocuments'] = async ( + accessToken, + sourceConfig, + cursor, + syncContext + ) => { + permissionStartedAt = undefined + const signal = syncContext?.signal instanceof AbortSignal ? syncContext.signal : undefined + signal?.throwIfAborted() + if (cursor && !cursor.startsWith(CURSOR_PREFIX)) { + return { + documents: [], + hasMore: true, + nextCursor: nextCursor(initial(), { enqueue: adapter.resume(cursor) }), + } + } + let state = cursor ? readCursor(cursor) : initial() + if ( + !state.active && + input.syncIntervalMinutes > 0 && + state.directoryComplete && + state.directoryRefreshAt && + new Date(state.directoryRefreshAt) <= now() + ) { + state = { ...state, directoryComplete: false, directoryCursor: undefined, phase: 'directory' } + } + const selected = selectedGoogleWorkspaceUsers(sourceConfig.userEmails) + if (!state.active && !state.directoryComplete && state.phase === 'directory') { + let page: Awaited> + try { + page = await listGoogleWorkspaceUsers(accessToken, state.directoryCursor, signal) + } catch (error) { + if ( + !state.directoryCursor || + !(error instanceof ConnectorSourceError) || + error.status !== 400 + ) + throw error + /** Re-enumeration is idempotent and preserves every already committed user continuation. */ + return { + documents: [], + hasMore: true, + nextCursor: nextCursor({ ...state, directoryCursor: undefined }, {}), + } + } + const users = page.users.filter( + (user) => user.active && (!selected.length || selected.includes(user.email)) + ) + return { + documents: [], + currentCursor: writeCursor(state), + hasMore: true, + nextCursor: nextCursor( + { + ...state, + phase: 'users', + directoryCursor: page.nextPageToken, + directoryComplete: !page.nextPageToken, + directoryRefreshAt: page.nextPageToken + ? undefined + : new Date(now().getTime() + DIRECTORY_REFRESH_MS).toISOString(), + }, + { + enqueue: users.map(({ id, email, customerId }) => { + const user = { id, email, customerId } + return { user, cursor: adapter.seed(user) } + }), + } + ), + } + } + const preferred = state.permissionTurn ? 'permissions' : 'content' + const work = state.active + ? await input.store.get(state.active.userId, state.active.kind) + : ((await input.store.next(preferred, now())) ?? + (await input.store.next(preferred === 'content' ? 'permissions' : 'content', now()))) + if (!work) { + if (state.active) throw new Error('Google company crawl lost its durable user checkpoint') + if (!state.directoryComplete) + return { + documents: [], + hasMore: true, + nextCursor: nextCursor({ ...state, phase: 'directory' }, {}), + } + const remaining = await input.store.remaining() + return { + documents: [], + hasMore: remaining.count > 0, + ...(remaining.count > 0 + ? { + nextCursor: nextCursor(state, {}), + resumeAt: new Date( + Math.min( + remaining.retryAt?.getTime() ?? now().getTime() + 60_000, + input.syncIntervalMinutes > 0 && state.directoryRefreshAt + ? new Date(state.directoryRefreshAt).getTime() + : Number.POSITIVE_INFINITY + ) + ).toISOString(), + } + : {}), + listingFailures: remaining.failures ?? null, + ...(remaining.failures ? { reconciliationSafe: false } : {}), + } + } + const active = { + ...state, + revision: state.revision + 1, + active: { userId: work.user.id, kind: work.kind }, + } + const currentCursor = writeCursor(active) + const next = { + ...state, + revision: active.revision, + phase: state.directoryComplete ? 'users' : 'directory', + permissionTurn: work.kind === 'content', + } as CompanyWorkCursor + const failed = async ( + failure: ListingFailure, + unresolved = false, + resetCursor = false + ): Promise => { + const previous = await input.store.remaining() + const retryAt = new Date( + now().getTime() + + (!unresolved ? 60 : Math.min(60, 5 * 2 ** Math.min(work.attempts, 4))) * 60_000 + ) + if (unresolved) unresolvedFailures += 1 + const failureSamples = [ + failure, + ...(previous.failures?.samples ?? []).filter((item) => item.scope !== failure.scope), + ] + return { + documents: [], + currentCursor, + hasMore: true, + nextCursor: nextCursor(next, { + update: { + userId: work.user.id, + kind: work.kind, + cursor: resetCursor ? adapter.seed(work.user) : (work.cursor ?? null), + completed: false, + retryAt, + attempts: work.attempts + 1, + failure, + ...(resetCursor && work.kind === 'permissions' ? { permissionStartedAt: null } : {}), + }, + }), + reconciliationSafe: false, + listingFailures: { + count: (previous.failures?.count ?? 0) + (work.hasFailure ? 0 : 1), + samples: failureSamples.slice(0, MAX_LISTING_FAILURE_SAMPLES), + }, + ...(unresolvedFailures >= MAX_UNRESOLVED_FAILURES_PER_PASS + ? { resumeAt: new Date(now().getTime() + 5 * 60_000).toISOString() } + : {}), + } + } + let page: ExternalDocumentList + try { + page = await input.listDocuments( + accessToken, + sourceConfig, + work.cursor ?? adapter.seed(work.user), + syncContext + ) + } catch (error) { + signal?.throwIfAborted() + if (input.isListingCursorInvalidError?.(error)) + return failed( + { scope: work.user.email, operation: 'google.user.cursor', reasons: [] }, + false, + true + ) + const failure = deferredUserFailure(error, work.user) + if (!failure) throw error + return failed(failure, true) + } + if (page.listingFailures) + return failed( + page.listingFailures.samples[0] ?? { + scope: work.user.email, + operation: 'google.user.list', + reasons: [], + } + ) + if ( + page.hasMore && + (!page.nextCursor || page.nextCursor === (page.currentCursor ?? work.cursor)) + ) { + throw new Error('Google user listing did not advance its provider continuation') + } + unresolvedFailures = 0 + permissionStartedAt = + work.kind === 'permissions' ? (work.permissionStartedAt ?? now()) : undefined + if (page.currentCursor) + changes.set(currentCursor, { + pin: { + userId: work.user.id, + kind: work.kind, + cursor: page.currentCursor, + permissionStartedAt, + }, + }) + return { + ...page, + currentCursor, + hasMore: true, + ...(work.kind === 'permissions' ? { permissionsOnly: true } : {}), + nextCursor: nextCursor(next, { + update: { + userId: work.user.id, + kind: work.kind, + cursor: page.nextCursor ?? null, + completed: !page.hasMore, + attempts: 0, + failure: null, + retryAt: !page.hasMore + ? new Date( + now().getTime() + + (work.kind === 'permissions' + ? PERMISSION_REFRESH_MS + : input.syncIntervalMinutes * 60_000) + ) + : now(), + ...(work.kind === 'permissions' + ? { permissionStartedAt: page.hasMore ? permissionStartedAt : null } + : {}), + }, + }), + } + } + return { + listDocuments, + permissionStartedAt: () => permissionStartedAt, + changesFor: (cursor: string | null): GoogleCompanyWorkChanges | undefined => + cursor ? changes.get(cursor) : undefined, + didCommit: (cursor: string | null) => { + if (cursor) changes.delete(cursor) + }, + } +} diff --git a/apps/sim/lib/knowledge/connectors/google-company-store.ts b/apps/sim/lib/knowledge/connectors/google-company-store.ts new file mode 100644 index 00000000000..5ea1f37971f --- /dev/null +++ b/apps/sim/lib/knowledge/connectors/google-company-store.ts @@ -0,0 +1,189 @@ +import { db } from '@sim/db' +import { knowledgeConnectorGoogleUser } from '@sim/db/schema' +import { and, asc, eq, exists, isNotNull, lte, ne, or, sql } from 'drizzle-orm' +import type { DbOrTx } from '@/lib/db/types' +import type { + GoogleCompanyWorkChanges, + GoogleCompanyWorkItem, + GoogleCompanyWorkKind, + GoogleCompanyWorkStore, +} from '@/lib/knowledge/connectors/google-company-scheduler' +import { listingFailuresSchema, MAX_LISTING_FAILURE_SAMPLES } from '@/connectors/listing-failures' + +const PERMISSION_REFRESH_MS = 12 * 60 * 60 * 1000 +type Row = typeof knowledgeConnectorGoogleUser.$inferSelect +const work = knowledgeConnectorGoogleUser + +function project(row: Row, kind: GoogleCompanyWorkKind): GoogleCompanyWorkItem { + return { + user: { id: row.userId, email: row.email, customerId: row.customerId }, + kind, + cursor: (kind === 'content' ? row.cursor : row.permissionCursor) ?? undefined, + attempts: kind === 'content' ? row.attempts : row.permissionAttempts, + hasFailure: row.failure !== null || row.permissionFailure !== null, + permissionStartedAt: row.permissionStartedAt ?? undefined, + } +} + +/** The connector's existing lease is the sole writer lease; no extra worker concurrency is introduced. */ +export function googleCompanyWorkStore( + connectorId: string, + generationId: string, + rescanCompleted = true +): GoogleCompanyWorkStore { + const scope = and(eq(work.connectorId, connectorId), eq(work.generationId, generationId)) + return { + async get(userId, kind) { + const [row] = await db + .select() + .from(work) + .where(and(scope, eq(work.userId, userId))) + .limit(1) + return row ? project(row, kind) : null + }, + async next(kind, now) { + /** A completed sweep must reach EOF instead of starting another overdue user sweep. */ + const incomplete = ne(work.status, 'complete') + const contentEligible = rescanCompleted + ? or( + incomplete, + exists(db.select({ id: work.userId }).from(work).where(and(scope, incomplete))) + ) + : incomplete + const [row] = await db + .select() + .from(work) + .where( + and( + scope, + kind === 'content' + ? and(lte(work.retryAt, now), contentEligible) + : and(isNotNull(work.lastServedAt), lte(work.permissionRetryAt, now)) + ) + ) + .orderBy( + sql`${kind === 'content' ? work.lastServedAt : work.permissionLastServedAt} ASC NULLS FIRST`, + asc(work.userId) + ) + .limit(1) + return row ? project(row, kind) : null + }, + async remaining() { + const [counts] = await db + .select({ + count: sql`count(*) FILTER (WHERE ${work.status} <> 'complete')::int`, + failures: sql`count(*) FILTER (WHERE ${work.failure} IS NOT NULL OR ${work.permissionFailure} IS NOT NULL)::int`, + retryAt: rescanCompleted + ? sql`min(${work.retryAt})::text` + : sql< + string | null + >`min(${work.retryAt}) FILTER (WHERE ${work.status} <> 'complete')::text`, + }) + .from(work) + .where(scope) + const samples = counts?.failures + ? await db + .select({ failure: work.failure, permissionFailure: work.permissionFailure }) + .from(work) + .where(and(scope, or(isNotNull(work.failure), isNotNull(work.permissionFailure)))) + .orderBy(asc(work.userId)) + .limit(MAX_LISTING_FAILURE_SAMPLES) + : [] + return { + count: counts?.count ?? 0, + retryAt: counts?.retryAt ? new Date(counts.retryAt) : null, + ...(counts?.failures + ? { + failures: listingFailuresSchema.parse({ + count: counts.failures, + samples: samples.map((row) => row.failure ?? row.permissionFailure), + }), + } + : {}), + } + }, + } +} + +/** Called in the same transaction as the listing checkpoint, after that page's durable work. */ +export async function commitGoogleCompanyWork( + tx: DbOrTx, + connectorId: string, + generationId: string, + changes: GoogleCompanyWorkChanges, + generationStartedAt: Date +): Promise { + const scope = and(eq(work.connectorId, connectorId), eq(work.generationId, generationId)) + if (changes.enqueue?.length) { + const at = new Date() + await tx + .insert(work) + .values( + changes.enqueue.map(({ user, cursor }) => ({ + connectorId, + generationId, + userId: user.id, + email: user.email, + customerId: user.customerId, + cursor: cursor ?? null, + permissionRetryAt: new Date(generationStartedAt.getTime() + PERMISSION_REFRESH_MS), + })) + ) + .onConflictDoUpdate({ + target: [work.connectorId, work.userId], + set: { + generationId, + email: sql`excluded.email`, + customerId: sql`excluded.customer_id`, + cursor: sql`excluded.cursor`, + status: 'pending', + attempts: 0, + retryAt: at, + lastServedAt: null, + failure: null, + permissionCursor: null, + permissionStartedAt: null, + permissionFailure: null, + permissionAttempts: 0, + }, + setWhere: ne(work.generationId, generationId), + }) + } + if (changes.pin) { + const pin = changes.pin + await tx + .update(work) + .set( + pin.kind === 'content' + ? { cursor: pin.cursor } + : { permissionCursor: pin.cursor, permissionStartedAt: pin.permissionStartedAt } + ) + .where(and(scope, eq(work.userId, pin.userId))) + } + if (changes.update) { + const update = changes.update + const at = new Date() + await tx + .update(work) + .set( + update.kind === 'content' + ? { + cursor: update.cursor, + status: update.failure ? 'blocked' : update.completed ? 'complete' : 'pending', + retryAt: update.retryAt, + attempts: update.attempts, + failure: update.failure, + lastServedAt: at, + } + : { + permissionCursor: update.cursor, + permissionRetryAt: update.retryAt, + permissionAttempts: update.attempts, + permissionFailure: update.failure, + permissionLastServedAt: at, + permissionStartedAt: update.permissionStartedAt, + } + ) + .where(and(scope, eq(work.userId, update.userId))) + } +} diff --git a/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts b/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts index 476a8bfb86a..47cc5ed4b00 100644 --- a/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts +++ b/apps/sim/lib/knowledge/connectors/listing-checkpoint.ts @@ -31,6 +31,7 @@ const checkpointSchema = z.object({ incrementalSince: z.string().datetime().nullable(), forceRehydrate: z.boolean(), fullSync: z.boolean().default(false), + resumeAt: z.string().datetime().nullable().default(null), }) export type ListingCheckpoint = z.infer @@ -75,6 +76,7 @@ export function beginListingCheckpoint(input: { incrementalSince: input.incrementalSince?.toISOString() ?? null, forceRehydrate: input.forceRehydrate ?? false, fullSync: input.fullSync ?? false, + resumeAt: null, } } @@ -99,7 +101,8 @@ export async function runResumableListing(input: { /** False retains this page's cursor after a bounded amount of durable work. */ processPage: ( documents: ExternalDocument[], - checkpoint: ListingCheckpoint + checkpoint: ListingCheckpoint, + response: ExternalDocumentList ) => Promise saveCheckpoint: (checkpoint: ListingCheckpoint) => Promise }): Promise { @@ -156,14 +159,17 @@ export async function runResumableListing(input: { } checkpoint.unsafe ||= response.reconciliationSafe === false || - response.listingFailures !== undefined || + response.listingFailures != null || Boolean( input.syncContext.listingCapped || input.syncContext.listingTruncated || input.syncContext.reconciliationUnsafe ) - if (response.listingFailures) { - checkpoint.listingFailures = listingFailuresSchema.parse(response.listingFailures) + if (response.listingFailures !== undefined) { + checkpoint.listingFailures = + response.listingFailures === null + ? null + : listingFailuresSchema.parse(response.listingFailures) } if (response.currentCursor !== undefined && response.currentCursor !== checkpoint.cursor) { if (response.currentCursor.length > 512 * 1024) @@ -172,7 +178,7 @@ export async function runResumableListing(input: { await input.saveCheckpoint(checkpoint) cursors.add(response.currentCursor) } - if ((await input.processPage(response.documents, checkpoint)) === false) { + if ((await input.processPage(response.documents, checkpoint, response)) === false) { await input.saveCheckpoint(checkpoint) break } @@ -180,11 +186,14 @@ export async function runResumableListing(input: { ...checkpoint, cursor: response.nextCursor ?? null, complete: !response.hasMore, - listedCount: checkpoint.listedCount + response.documents.length, + listedCount: + checkpoint.listedCount + (response.permissionsOnly ? 0 : response.documents.length), + resumeAt: response.resumeAt ?? null, } await input.saveCheckpoint(next) checkpoint = next if (checkpoint.cursor) cursors.add(checkpoint.cursor) + if (checkpoint.resumeAt) break } return checkpoint } diff --git a/apps/sim/lib/knowledge/connectors/member-sync-engine.ts b/apps/sim/lib/knowledge/connectors/member-sync-engine.ts index addef3346e4..f0e34dd6e53 100644 --- a/apps/sim/lib/knowledge/connectors/member-sync-engine.ts +++ b/apps/sim/lib/knowledge/connectors/member-sync-engine.ts @@ -1446,7 +1446,7 @@ async function completeMemberSync( .select({ id: knowledgeBase.id }) .from(knowledgeBase) .where(and(eq(knowledgeBase.id, run.knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .for('update') + .for('share') if (!activeKnowledgeBase) { /** Nothing to record against a deleted knowledge base; hand the lease back rather than let it expire as a failure. */ await tx 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 59b87aca767..ad981f4657c 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts @@ -288,6 +288,8 @@ async function runPass( checkpoint?: ListingCheckpoint databaseTime?: Date getDocument?: () => Promise + permissionsOnly?: boolean + permissionStoredAfter?: StoredPage } = {} ) { vi.clearAllMocks() @@ -320,6 +322,11 @@ async function runPass( queueTableRows(schemaMock.document, [{ fileUrl: options.existing?.fileUrl ?? '' }]) } } + if (options.permissionsOnly) + queueTableRows( + schemaMock.document, + options.permissionStoredAfter ? [options.permissionStoredAfter] : [] + ) queueTableRows(schemaMock.document, [ { ownedCount: 1, listedCount: 1, softCount: 0, hardCount: 0 }, ]) @@ -346,7 +353,14 @@ async function runPass( options.getDocument ?? (() => confluenceConnector.getDocument('token', SOURCE_CONFIG, 'page', syncContext)) ) - const listDocuments = vi.fn(confluenceConnector.listDocuments) + const listDocuments = vi.fn( + async (...args: Parameters) => { + const page = await confluenceConnector.listDocuments(...args) + return options.permissionsOnly + ? { ...page, permissionsOnly: true, reconciliationSafe: false } + : page + } + ) const pass = await runConnectorContentPass({ connectorId: 'connector', connector: { @@ -385,6 +399,28 @@ function contentWrite(): Record { } describe('content pass checkpoint intent', () => { + it('preserves non-Google replay behavior for already-seen documents', async () => { + sourceBody = { value: '

Updated body

' } + const checkpoint = beginListingCheckpoint({ + fingerprint: 'a'.repeat(64), + generationId: 'incomplete-company', + startedAt: new Date('2026-09-07T12:00:00Z'), + }) + const { hydrate, result } = await runPass({ + access: 'admin', + readCurrent: true, + checkpoint, + existing: { + ...EXISTING, + contentHash: 'old-body', + storageKey: 'kb/old.txt', + sourceSeenAt: new Date(checkpoint.startedAt), + }, + }) + expect(hydrate).not.toHaveBeenCalled() + expect(result.docsUpdated).toBe(0) + }) + it('does not reconcile deletions after a user listing failed, even without the unsafe marker', async () => { sourceBody = { value: '

Current content

' } const checkpoint = { @@ -566,6 +602,89 @@ describe('content pass checkpoint intent', () => { ) }) +describe('permission refresh through the shared content pass', () => { + const current: StoredPage = { + ...EXISTING, + contentHash: 'confluence:storage-local-body-v2:page:3', + storageKey: 'kb/current.txt', + sourceSeenAt: new Date('2026-09-07T12:00:00Z'), + } + + it('refreshes unchanged stored bodies without hydration, new content, counts, or absence reconciliation', async () => { + const { pass, result, hydrate } = await runPass({ + access: 'admin', + existing: current, + permissionsOnly: true, + permissionStoredAfter: current, + }) + expect(hydrate).not.toHaveBeenCalled() + expect(mocks.onPage).toHaveBeenCalledWith( + [expect.objectContaining({ externalId: 'page' })], + expect.any(Date) + ) + expect(pass.checkpoint.listedCount).toBe(0) + expect(result).toMatchObject({ docsAdded: 0, docsUpdated: 0, docsDeleted: 0 }) + expect( + dbChainMockFns.set.mock.calls.some( + ([values]) => 'sourceSeenAt' in values || 'deletedAt' in values + ) + ).toBe(false) + expect(mocks.hardDelete).not.toHaveBeenCalled() + }) + + it('ignores documents not previously stored instead of indexing them during permission refresh', async () => { + const { hydrate, result } = await runPass({ access: 'admin', permissionsOnly: true }) + expect(hydrate).not.toHaveBeenCalled() + expect(mocks.onPage).toHaveBeenCalledWith([], expect.any(Date)) + expect(result.docsAdded).toBe(0) + expect(mocks.upload).not.toHaveBeenCalled() + }) + + it('revokes an old body before repairing a changed document and renews permission only for the persisted version', async () => { + sourceBody = { value: '

Current safe body

' } + const { hydrate, result } = await runPass({ + access: 'admin', + permissionsOnly: true, + readCurrent: true, + existing: { ...current, contentHash: 'old-body' }, + permissionStoredAfter: current, + }) + expect(hydrate).toHaveBeenCalledOnce() + expect(dbChainMockFns.set).toHaveBeenCalledWith({ + acl: [], + aclRequirements: [], + aclVerifiedAt: null, + sourceSeenAt: null, + }) + expect(result.docsUpdated).toBe(1) + expect(mocks.onPage).toHaveBeenCalledWith( + [expect.objectContaining({ contentHash: current.contentHash })], + expect.any(Date) + ) + }) + + it('never renews a changed body after its hydration fails', async () => { + const { hydrate, result } = await runPass({ + access: 'admin', + permissionsOnly: true, + existing: { ...current, contentHash: 'old-body' }, + getDocument: async () => { + throw new Error('provider unavailable') + }, + permissionStoredAfter: { ...current, contentHash: null }, + }) + expect(hydrate).toHaveBeenCalledOnce() + expect(result.docsFailed).toBe(1) + expect(dbChainMockFns.set).toHaveBeenCalledWith({ + acl: [], + aclRequirements: [], + aclVerifiedAt: null, + sourceSeenAt: null, + }) + expect(mocks.onPage).toHaveBeenCalledWith([], expect.any(Date)) + }) +}) + describe('Confluence empty content through the shared content pass', () => { it.each(['admin', 'members'] as const)( 'recovers a failed stub, reuses its verified empty version, and indexes an edited page for %s', diff --git a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts index 6fc86b716fb..6bf9faa6f3b 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts @@ -3,7 +3,18 @@ import { document, knowledgeConnector } from '@sim/db/schema' import { and, asc, eq, inArray, isNotNull, isNull, lt, type SQL, sql } from 'drizzle-orm' import type { BillingAttributionSnapshot } from '@/lib/billing/core/billing-attribution' import type { DbOrTx } from '@/lib/db/types' -import type { ConnectorAccessMode } from '@/lib/knowledge/connectors/access-modes' +import { + type ConnectorAccessMode, + effectiveConnectorSyncIntervalMinutes, +} from '@/lib/knowledge/connectors/access-modes' +import { + createGoogleCompanyScheduler, + isGoogleCompanySource, +} from '@/lib/knowledge/connectors/google-company-scheduler' +import { + commitGoogleCompanyWork, + googleCompanyWorkStore, +} from '@/lib/knowledge/connectors/google-company-store' import { beginListingCheckpoint, type ListingCheckpoint, @@ -33,6 +44,7 @@ import { resolveReconciliationDeleteCap, } from '@/lib/knowledge/connectors/sync-primitives' import { hardDeleteDocuments } from '@/lib/knowledge/documents/service' +import { SIM_SEARCH_SYNC_INTERVAL_MINUTES } from '@/lib/sim-search/constants' import type { ConnectorConfig, ExternalDocument, SyncResult } from '@/connectors/types' interface ContentPassInput { @@ -42,6 +54,7 @@ interface ContentPassInput { connectorType: string listingCheckpoint?: unknown lastSyncDocCount?: number | null + syncIntervalMinutes?: number } connectorConfig: ConnectorConfig sourceConfig: Record @@ -102,8 +115,33 @@ export async function runConnectorContentPass(input: ContentPassInput) { }) } let hydratedCount = 0 + const syncIntervalMinutes = effectiveConnectorSyncIntervalMinutes( + input.documentAccess, + input.connector.syncIntervalMinutes ?? SIM_SEARCH_SYNC_INTERVAL_MINUTES + ) + const companyStore = () => + googleCompanyWorkStore( + input.connectorId, + String(input.syncContext.syncRunId), + syncIntervalMinutes > 0 + ) + const company = isGoogleCompanySource(input.connector.connectorType, input.syncContext) + ? createGoogleCompanyScheduler({ + provider: input.connector.connectorType, + listDocuments: input.connectorConfig.listDocuments, + isListingCursorInvalidError: input.connectorConfig.isListingCursorInvalidError, + syncIntervalMinutes, + store: { + get: (...args) => companyStore().get(...args), + next: (...args) => companyStore().next(...args), + remaining: () => companyStore().remaining(), + }, + }) + : undefined checkpoint = await runResumableListing({ - connectorConfig: input.connectorConfig, + connectorConfig: company + ? { ...input.connectorConfig, listDocuments: company.listDocuments } + : input.connectorConfig, sourceConfig: input.sourceConfig, syncContext: input.syncContext, checkpoint, @@ -111,14 +149,50 @@ export async function runConnectorContentPass(input: ContentPassInput) { beforePage: input.lease.beatIfDue, getGenerationStartedAt: () => withLease(readGenerationStartedAt), getAccessToken: input.getAccessToken, - processPage: async (documents, cycle) => { + processPage: async (documents, cycle, page) => { + const corpus = await loadPageCorpus( + input.connectorId, + documents.map((item) => item.externalId) + ) + if (page.permissionsOnly) + documents = documents.filter((item) => corpus.priorByExternalId.has(item.externalId)) const externalIds = documents.map((item) => item.externalId) - const corpus = await loadPageCorpus(input.connectorId, externalIds) + if (page.permissionsOnly) { + const changed = documents.filter((item) => { + const prior = corpus.priorByExternalId.get(item.externalId) + return prior && (!prior.contentHash || prior.contentHash !== item.contentHash) + }) + /** A fresh grant must not publish an old body after the source document changed. */ + if (changed.length) + await withLease(async (tx) => { + for (let offset = 0; offset < changed.length; offset += 500) { + await tx + .update(document) + .set({ acl: [], aclRequirements: [], aclVerifiedAt: null, sourceSeenAt: null }) + .where( + and( + eq(document.connectorId, input.connectorId), + inArray( + document.externalId, + changed.slice(offset, offset + 500).map((item) => item.externalId) + ), + isNull(document.archivedAt) + ) + ) + } + }) + } const state = createSyncRunState(input.result) const startedAt = new Date(cycle.startedAt) const remaining = documents.filter((item) => { const prior = corpus.priorByExternalId.get(item.externalId) - if (!prior?.sourceSeenAt || prior.sourceSeenAt < startedAt) return true + if (page.permissionsOnly) return prior?.contentHash !== item.contentHash + if ( + !prior?.sourceSeenAt || + prior.sourceSeenAt < startedAt || + (company && prior.contentHash !== null && prior.contentHash !== item.contentHash) + ) + return true /** A crash after persisting a failed batch must not erase its failure evidence. */ if (prior.contentHash === null && !corpus.excludedExternalIds.has(item.externalId)) { cycle.contentFailures = true @@ -142,6 +216,7 @@ export async function runConnectorContentPass(input: ContentPassInput) { }) cycle.contentFailures = true } + if (page.permissionsOnly) return const attemptedIds = attempted.map((item) => item.externalId) await withLease(async (tx) => { for (let offset = 0; offset < attemptedIds.length; offset += 500) { @@ -161,7 +236,7 @@ export async function runConnectorContentPass(input: ContentPassInput) { const pendingOps = classifyListing({ externalDocs: remaining, corpus, - forceRehydrate: cycle.forceRehydrate, + forceRehydrate: !page.permissionsOnly && cycle.forceRehydrate, state, }) const pendingIds = new Set(pendingOps.map((op) => op.extDoc.externalId)) @@ -171,15 +246,32 @@ export async function runConnectorContentPass(input: ContentPassInput) { corpus, state, pendingOps, - forceRehydrate: cycle.forceRehydrate, + forceRehydrate: !page.permissionsOnly && cycle.forceRehydrate, onBatchComplete: async (attempted) => { hydratedCount += attempted.filter((item) => item.contentDeferred).length await persistAttempted(attempted) }, }) if (!finished) return false - const pageOutcome = await input.onPage?.(documents, startedAt) + const permissionCorpus = page.permissionsOnly + ? await loadPageCorpus(input.connectorId, externalIds) + : undefined + const verifiedDocuments = documents.filter((item) => { + if (!permissionCorpus) return true + if (state.failedExternalIds.has(item.externalId)) return false + const stored = permissionCorpus.priorByExternalId.get(item.externalId) + return Boolean( + stored?.contentHash && + stored.contentHash === item.contentHash && + stored.storageKey !== null + ) + }) + const pageOutcome = await input.onPage?.( + verifiedDocuments, + page.permissionsOnly ? (company?.permissionStartedAt() ?? startedAt) : startedAt + ) if (pageOutcome?.permissionsIncomplete) cycle.permissionFailures = true + if (page.permissionsOnly) return await withLease(async (tx) => { const verified = externalIds.filter((id) => !state.failedExternalIds.has(id)) for (let offset = 0; offset < verified.length; offset += 500) { @@ -197,13 +289,24 @@ export async function runConnectorContentPass(input: ContentPassInput) { } }) }, - saveCheckpoint: (next) => - withLease((tx) => - tx + saveCheckpoint: async (next) => { + await withLease(async (tx) => { + const changes = company?.changesFor(next.cursor) + if (changes) + await commitGoogleCompanyWork( + tx, + input.connectorId, + next.generationId, + changes, + new Date(next.startedAt) + ) + await tx .update(knowledgeConnector) .set({ listingCheckpoint: next }) .where(input.lease.stillHeld()) - ).then(() => undefined), + }) + company?.didCommit(next.cursor) + }, }) const reconciliation = checkpoint.complete ? await reconcileCompletedListing(input, checkpoint, withLease) diff --git a/apps/sim/lib/knowledge/connectors/sync-deferral.ts b/apps/sim/lib/knowledge/connectors/sync-deferral.ts index 1eef01466de..0d1e497b1c9 100644 --- a/apps/sim/lib/knowledge/connectors/sync-deferral.ts +++ b/apps/sim/lib/knowledge/connectors/sync-deferral.ts @@ -53,7 +53,7 @@ export async function deferConnectorSync(input: { .select({ id: knowledgeBase.id }) .from(knowledgeBase) .where(and(eq(knowledgeBase.id, input.knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .for('update') + .for('share') if (!liveKnowledgeBase) throw new SyncLockLostException(input.connectorId) const [held] = await tx .select({ id: knowledgeConnector.id }) diff --git a/apps/sim/lib/knowledge/connectors/sync-engine.test.ts b/apps/sim/lib/knowledge/connectors/sync-engine.test.ts index 29ad7d9d714..ae0efed77df 100644 --- a/apps/sim/lib/knowledge/connectors/sync-engine.test.ts +++ b/apps/sim/lib/knowledge/connectors/sync-engine.test.ts @@ -3422,7 +3422,7 @@ describe('executeSync terminal exits under a lost lock', () => { expect(dbChainMockFns.set).not.toHaveBeenCalledWith( expect.objectContaining({ status: 'active', consecutiveFailures: 0 }) ) - expect(dbChainMockFns.for).toHaveBeenCalledWith('update') + expect(dbChainMockFns.for).toHaveBeenCalledWith('share') }) it.each(['checkpoint', 'batch'] as const)( diff --git a/apps/sim/lib/knowledge/connectors/sync-engine.ts b/apps/sim/lib/knowledge/connectors/sync-engine.ts index 5632d2e6a43..c39b5524d43 100644 --- a/apps/sim/lib/knowledge/connectors/sync-engine.ts +++ b/apps/sim/lib/knowledge/connectors/sync-engine.ts @@ -305,6 +305,7 @@ export interface ContentPassOutcome { startedAt: string listedCount: number incrementalSince?: string | null + resumeAt?: string | null } } @@ -371,7 +372,7 @@ export async function completeSuccessfulSync( .select({ id: knowledgeBase.id }) .from(knowledgeBase) .where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .for('update') + .for('share') if (!lockedKnowledgeBase) throw new SyncCompletionOwnershipLost() const [lockedConnector] = await tx @@ -447,7 +448,11 @@ export async function completeSuccessfulSync( ...buildSyncSuccessUpdate( now, actualDocCount, - contentPass && !contentPass.complete ? now : calculateNextSyncTime(syncIntervalMinutes), + contentPass && !contentPass.complete + ? contentPass.checkpoint.resumeAt + ? new Date(contentPass.checkpoint.resumeAt) + : now + : calculateNextSyncTime(syncIntervalMinutes), completionNotice, result.docsFailed === 0 && (!contentPass || !isContentPassIncomplete(contentPass)) ), diff --git a/apps/sim/lib/knowledge/connectors/sync-primitives.ts b/apps/sim/lib/knowledge/connectors/sync-primitives.ts index 719144ae011..3a7ecb59b27 100644 --- a/apps/sim/lib/knowledge/connectors/sync-primitives.ts +++ b/apps/sim/lib/knowledge/connectors/sync-primitives.ts @@ -1392,7 +1392,7 @@ export async function sweepStuckDocuments(input: SweepStuckDocumentsInput): Prom .select({ id: knowledgeBase.id }) .from(knowledgeBase) .where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .for('update') + .for('share') if (!activeKnowledgeBase) throw new SyncLockLostException(connectorId) const [heldSyncLock] = await tx diff --git a/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts b/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts index c6131154e15..17863d0cb6c 100644 --- a/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts +++ b/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts @@ -3,8 +3,49 @@ import { describe, expect, it } from 'vitest' import { assertOcrSourceSupported } from '@/lib/knowledge/documents/ocr-source-validation' const GIF = Buffer.from('R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', 'base64') +const LFS_POINTER = `version https://git-lfs.github.com/spec/v1\noid sha256:${'a'.repeat(64)}\nsize 9566\n` describe('OCR source preflight', () => { + it.each(['image/png', 'image/jpeg', 'image/gif', 'application/pdf'])( + 'reports missing Git LFS content for %s without classifying it as a provider failure', + (mimeType) => { + expect(() => assertOcrSourceSupported(Buffer.from(LFS_POINTER), mimeType)).toThrow( + expect.objectContaining({ + name: 'PermanentDocumentProcessingError', + code: 'invalid_file', + message: expect.stringContaining('Git LFS pointer'), + }) + ) + } + ) + it('recognizes a pointer exported with Windows line endings', () => { + expect(() => + assertOcrSourceSupported(Buffer.from(LFS_POINTER.replaceAll('\n', '\r\n')), 'image/png') + ).toThrow(expect.objectContaining({ code: 'invalid_file' })) + }) + it('preserves small images and files containing a pointer as text', () => { + const png = Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/l9sAAAAASUVORK5CYII=', + 'base64' + ) + expect(png.length).toBeLessThan(Buffer.byteLength(LFS_POINTER)) + expect(() => assertOcrSourceSupported(png, 'image/png')).not.toThrow() + expect(() => assertOcrSourceSupported(Buffer.from(LFS_POINTER), 'text/plain')).not.toThrow() + expect(() => + assertOcrSourceSupported(Buffer.concat([png, Buffer.from(LFS_POINTER)]), 'image/png') + ).not.toThrow() + }) + it('requires a complete pointer rather than matching a version URL alone', () => { + for (const content of [ + 'version https://git-lfs.github.com/spec/v1\n', + LFS_POINTER.replace('sha256:', 'sha1:'), + LFS_POINTER.replace('9566', 'unknown'), + `${LFS_POINTER}additional content`, + `${LFS_POINTER}${'x'.repeat(1024)}`, + ]) { + expect(() => assertOcrSourceSupported(Buffer.from(content), 'image/png')).not.toThrow() + } + }) it('accepts static GIFs without decoding their pixels', () => { expect(() => assertOcrSourceSupported(GIF, 'image/gif')).not.toThrow() }) diff --git a/apps/sim/lib/knowledge/documents/ocr-source-validation.ts b/apps/sim/lib/knowledge/documents/ocr-source-validation.ts index 835c3c199e0..f6e8ebed8d9 100644 --- a/apps/sim/lib/knowledge/documents/ocr-source-validation.ts +++ b/apps/sim/lib/knowledge/documents/ocr-source-validation.ts @@ -1,5 +1,10 @@ import { PermanentDocumentProcessingError } from '@/lib/knowledge/documents/document-processing-error' +/** Canonical Git LFS pointers are text records smaller than 1 KiB, not the referenced binary. */ +const GIT_LFS_POINTER_MAX_BYTES = 1024 +const GIT_LFS_POINTER_PATTERN = + /^version https:\/\/git-lfs\.github\.com\/spec\/v1\r?\noid sha256:[a-f0-9]{64}\r?\nsize [0-9]+\r?\n$/ + function invalidSource(message: string): never { throw new PermanentDocumentProcessingError('invalid_file', message) } @@ -61,6 +66,15 @@ function assertStaticGif(buffer: Buffer): void { /** Rejects proven input failures before they consume provider admission or paid OCR. */ export function assertOcrSourceSupported(buffer: Buffer, mimeType: string): void { + if ( + (mimeType === 'application/pdf' || mimeType.startsWith('image/')) && + buffer.length < GIT_LFS_POINTER_MAX_BYTES && + GIT_LFS_POINTER_PATTERN.test(buffer.toString('utf8')) + ) { + invalidSource( + 'This file contains a Git LFS pointer instead of the actual PDF or image. Upload the original file content and retry.' + ) + } if (mimeType === 'application/pdf' && !buffer.subarray(0, 1024).includes(Buffer.from('%PDF-'))) { invalidSource('This file is not a valid PDF. Re-export it as a PDF and retry.') } diff --git a/apps/sim/lib/knowledge/documents/pdf-ocr-triage.test.ts b/apps/sim/lib/knowledge/documents/pdf-ocr-triage.test.ts index 723bf3bc6f3..11fa8e213b8 100644 --- a/apps/sim/lib/knowledge/documents/pdf-ocr-triage.test.ts +++ b/apps/sim/lib/knowledge/documents/pdf-ocr-triage.test.ts @@ -43,6 +43,7 @@ vi.mock('@/lib/internal/mistral/operations', () => ({ })) import { env } from '@/lib/core/config/env' +import { waitForProviderAdmission } from '@/lib/core/rate-limiter/provider-admission' import { ProviderCapacityDeferredError } from '@/lib/core/rate-limiter/provider-capacity-error' import { FileParserError } from '@/lib/file-parsers/errors' import { MistralOperationError } from '@/lib/internal/mistral/errors' @@ -206,6 +207,39 @@ describe('PDF OCR triage', () => { } ) + it.each(['mistral', 'azure-mistral'])( + 'rejects a Git LFS pointer before %s admission or OCR', + async (provider) => { + Object.assign(env, { + OCR_PROVIDER: provider, + OCR_AZURE_API_KEY: 'key', + OCR_AZURE_ENDPOINT: 'https://example.openai.azure.com', + OCR_AZURE_MODEL_NAME: 'mistral-document-ai-2512', + }) + const bytes = Buffer.from( + `version https://git-lfs.github.com/spec/v1\noid sha256:${'b'.repeat(64)}\nsize 9566\n` + ) + mockDownload.mockResolvedValue(bytes) + const fetchMock = vi.fn() + vi.stubGlobal('fetch', fetchMock) + + await expect( + processDocument('https://example.com/image.png', 'image.png', 'image/png', 1024, 0, 1, { + userId: 'user-1', + }) + ).rejects.toMatchObject({ + name: 'PermanentDocumentProcessingError', + code: 'invalid_file', + message: expect.stringContaining('Git LFS pointer'), + }) + expect(mockDownload).toHaveBeenCalledOnce() + expect(waitForProviderAdmission).not.toHaveBeenCalled() + expect(mockExecuteMistralParse).not.toHaveBeenCalled() + expect(fetchMock).not.toHaveBeenCalled() + expect(mockParseBuffer).not.toHaveBeenCalled() + } + ) + it('uses the Azure image_url envelope for an image source', async () => { Object.assign(env, { OCR_PROVIDER: 'azure-mistral', diff --git a/apps/sim/lib/knowledge/documents/processing-recovery.ts b/apps/sim/lib/knowledge/documents/processing-recovery.ts index 8d558070ff1..35cee7413cb 100644 --- a/apps/sim/lib/knowledge/documents/processing-recovery.ts +++ b/apps/sim/lib/knowledge/documents/processing-recovery.ts @@ -2,7 +2,7 @@ import { db } from '@sim/db' import { document, knowledgeBase, knowledgeConnector } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId } from '@sim/utils/id' -import { and, asc, eq, inArray, isNull, sql } from 'drizzle-orm' +import { and, asc, eq, inArray, isNull, notInArray, sql } from 'drizzle-orm' import { assertBillingAttributionOwner, resolveSystemBillingAttribution, @@ -23,6 +23,7 @@ const logger = createLogger('KnowledgeDocumentRecovery') export const KNOWLEDGE_DOCUMENT_RECOVERY_OUTBOX_EVENT = 'knowledge.document.processing.recover' export const DOCUMENT_RECOVERY_BATCH_SIZE = 200 const RECOVERY_RUNTIME_MS = 20_000 +const MAX_RECOVERY_CANDIDATE_BATCHES = 4 const RECOVERABLE_CONNECTOR_STATUSES = ['active', 'error', 'pending', 'syncing'] /** @@ -40,6 +41,31 @@ async function recoverStoredDocuments( deadlineAt: number, signal: AbortSignal ): Promise { + let recovered = 0 + const attemptedKnowledgeBases = new Set() + for (let batch = 0; batch < MAX_RECOVERY_CANDIDATE_BATCHES; batch++) { + if (recovered >= DOCUMENT_RECOVERY_BATCH_SIZE || Date.now() >= deadlineAt) break + const ownersBefore = attemptedKnowledgeBases.size + recovered += await recoverStoredDocumentBatch( + now, + deadlineAt, + signal, + attemptedKnowledgeBases, + DOCUMENT_RECOVERY_BATCH_SIZE - recovered + ) + if (attemptedKnowledgeBases.size === ownersBefore) break + } + return recovered +} + +async function recoverStoredDocumentBatch( + now: Date, + deadlineAt: number, + signal: AbortSignal, + attemptedKnowledgeBases: Set, + limit: number +): Promise { + /** Discovery does not claim work. Ownership is rechecked under lifecycle locks below. */ const candidates = await db.transaction(async (tx) => { signal.throwIfAborted() await tx.execute( @@ -60,6 +86,9 @@ async function recoverStoredDocuments( .where( and( documentProcessingRecoveryCondition(now), + attemptedKnowledgeBases.size > 0 + ? notInArray(document.knowledgeBaseId, [...attemptedKnowledgeBases]) + : undefined, isNull(knowledgeBase.deletedAt), isNull(knowledgeConnector.deletedAt), isNull(knowledgeConnector.archivedAt), @@ -67,10 +96,10 @@ async function recoverStoredDocuments( ) ) .orderBy(asc(document.uploadedAt), asc(document.id)) - .limit(DOCUMENT_RECOVERY_BATCH_SIZE) - .for('update', { of: knowledgeBase, skipLocked: true }) + .limit(limit) }) signal.throwIfAborted() + if (candidates.length === 0) return 0 let recovered = 0 const groups = new Map() @@ -81,6 +110,7 @@ async function recoverStoredDocuments( } for (const [knowledgeBaseId, group] of groups) { if (Date.now() >= deadlineAt) break + attemptedKnowledgeBases.add(knowledgeBaseId) const owner = group[0] try { signal.throwIfAborted() @@ -107,7 +137,7 @@ async function recoverStoredDocuments( }) .from(knowledgeBase) .where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt))) - .for('update', { skipLocked: true }) + .for('share', { skipLocked: true }) signal.throwIfAborted() if (!kb) return 0 assertBillingAttributionOwner(attribution, kb) diff --git a/apps/sim/lib/oauth/credential-service.test.ts b/apps/sim/lib/oauth/credential-service.test.ts index a9f10ec759f..cd388def9c3 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, + resolveServiceAccountToken, ServiceAccountTokenError, } from '@/lib/oauth/credential-service' import { GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID } from '@/lib/oauth/types' @@ -414,6 +415,7 @@ describe('Google service-account token minting', () => { redirect: 'error', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: expect.any(URLSearchParams), + signal: expect.any(AbortSignal), }) const body = fetchMock.mock.calls[0][1]?.body expect(body).toBeInstanceOf(URLSearchParams) @@ -455,6 +457,23 @@ describe('Google service-account token minting', () => { expect(fetchMock).toHaveBeenCalledTimes(1) }) + it('preserves cancellation through the service-account resolver registry', async () => { + const controller = new AbortController() + const reason = new DOMException('Caller cancelled', 'AbortError') + controller.abort(reason) + await expect( + resolveServiceAccountToken( + 'credential-1', + GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID, + [driveScope], + 'member@example.com', + { signal: controller.signal } + ) + ).rejects.toBe(reason) + expect(mocks.decryptSecret).not.toHaveBeenCalled() + expect(fetchMock).not.toHaveBeenCalled() + }) + it('retains the Google error code for actionable setup failures', async () => { queueTableRows(credential, [row]) fetchMock.mockResolvedValueOnce( @@ -474,6 +493,13 @@ describe('Google service-account token minting', () => { errorCode: 'unauthorized_client', errorDescription: RAW_PROVIDER_ERROR, }) + expect(mocks.logger.error).toHaveBeenCalledWith('Service account token exchange failed', { + status: 401, + subject: 'admin@example.com', + scopes: [driveScope], + errorCode: 'unauthorized_client', + }) + expect(JSON.stringify(mocks.logger.error.mock.calls)).not.toContain(RAW_PROVIDER_ERROR) }) it('keeps token errors private for selectors', async () => { @@ -503,11 +529,11 @@ describe('Google service-account token minting', () => { '{"error_description":""}', ])('handles malformed provider errors without losing the HTTP status: %s', async (body) => { queueTableRows(credential, [row]) - fetchMock.mockResolvedValueOnce(new Response(body, { status: 503 })) + fetchMock.mockResolvedValueOnce(new Response(body, { status: 400 })) await expect(getServiceAccountToken('credential-1', [driveScope])).rejects.toMatchObject({ - statusCode: 503, + statusCode: 400, errorCode: undefined, - errorDescription: 'Token exchange failed: 503', + errorDescription: 'Token exchange failed: 400', }) }) @@ -525,4 +551,47 @@ describe('Google service-account token minting', () => { errorDescription: 'Invalid account credentials.', }) }) + + it('returns the existing typed error after bounded transient retries and keeps selectors private', async () => { + queueTableRows(credential, [row]) + fetchMock.mockImplementation(async () => + Response.json( + { + error: 'temporarily_unavailable', + error_description: RAW_PROVIDER_ERROR, + }, + { status: 503 } + ) + ) + const request = getServiceAccountToken('credential-1', [driveScope], 'private@example.com', { + privacyMode: 'selector', + }) + const checked = expect(request).rejects.toMatchObject({ + name: 'ServiceAccountTokenError', + statusCode: 503, + errorCode: undefined, + errorDescription: 'Token exchange failed: 503', + }) + await vi.runAllTimersAsync() + await checked + expect(fetchMock).toHaveBeenCalledTimes(3) + const logs = JSON.stringify([ + ...mocks.logger.info.mock.calls, + ...mocks.logger.warn.mock.calls, + ...mocks.logger.error.mock.calls, + ]) + expect(logs).not.toContain(RAW_PROVIDER_ERROR) + expect(logs).not.toContain('private@example.com') + expect(logs).not.toContain('crawler@qa-project.iam.gserviceaccount.com') + }) + + it('retains the HTTP status when an error payload exceeds the response limit', async () => { + queueTableRows(credential, [row]) + fetchMock.mockResolvedValueOnce(new Response('x'.repeat(100_000), { status: 400 })) + await expect(getServiceAccountToken('credential-1', [driveScope])).rejects.toMatchObject({ + statusCode: 400, + errorDescription: 'Token exchange failed: 400', + }) + expect(JSON.stringify(mocks.logger.error.mock.calls)).not.toContain('xxxx') + }) }) diff --git a/apps/sim/lib/oauth/credential-service.ts b/apps/sim/lib/oauth/credential-service.ts index 7cfee32eaea..ebfb51a8118 100644 --- a/apps/sim/lib/oauth/credential-service.ts +++ b/apps/sim/lib/oauth/credential-service.ts @@ -29,6 +29,7 @@ import { GITHUB_INSTALLATION_PROVIDER_ID, type GitHubInstallationRepositoryScope, } from '@/lib/oauth/github-installation-types' +import { exchangeGoogleServiceAccountJwt } from '@/lib/oauth/google-service-account-transport' import { isInstagramProvider, shouldProactivelyRefreshInstagramToken } from '@/lib/oauth/instagram' import { getMicrosoftRefreshTokenExpiry, @@ -73,6 +74,8 @@ export interface CredentialTokenResolutionOptions { privacyMode?: 'selector' /** GitHub installation content tokens may only address one connector repository. */ githubRepositoryScope?: GitHubInstallationRepositoryScope + /** Cancels Google service-account token exchange and retry waits. */ + signal?: AbortSignal } function privateCredentialIdentity(namespace: string, value: string): string { @@ -189,6 +192,19 @@ const SA_EXCLUDED_SCOPES = new Set([ 'https://www.googleapis.com/auth/userinfo.profile', ]) +/** Google's documented JWT error codes are safe to retain without the provider description. */ +const SA_DIAGNOSTIC_ERROR_CODES = new Set([ + 'access_denied', + 'admin_policy_enforced', + 'deleted_client', + 'disabled_client', + 'invalid_client', + 'invalid_grant', + 'invalid_scope', + 'org_internal', + 'unauthorized_client', +]) + /** * Generates a short-lived access token for a Google service account credential * using the two-legged OAuth JWT flow (RFC 7523). @@ -204,6 +220,7 @@ export async function getServiceAccountToken( impersonateEmail?: string, options?: CredentialTokenResolutionOptions ): Promise { + options?.signal?.throwIfAborted() const [credentialRow] = await db .select({ type: credential.type, @@ -267,6 +284,7 @@ export async function getServiceAccountToken( hasSubject: Boolean(impersonateEmail), scopes: filteredScopes.join(' '), aud: tokenUri, + subject: impersonateEmail, } ) @@ -280,22 +298,10 @@ export async function getServiceAccountToken( const jwt = `${signingInput}.${signature}` - const response = await fetch(tokenUri, { - method: 'POST', - redirect: 'error', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', - assertion: jwt, - }), - }) + const response = await exchangeGoogleServiceAccountJwt(tokenUri, jwt, options?.signal) if (!response.ok) { - const errorBody = await response.text() - logger.error('Service account token exchange failed', { - status: response.status, - ...(options?.privacyMode === 'selector' ? {} : { body: errorBody }), - }) + const errorBody = response.body let description = `Token exchange failed: ${response.status}` let errorCode: string | undefined if (options?.privacyMode !== 'selector') { @@ -322,10 +328,20 @@ export async function getServiceAccountToken( /** Retain the status-based description when Google returns a non-JSON error. */ } } + logger.error('Service account token exchange failed', { + status: response.status, + ...(options?.privacyMode === 'selector' + ? {} + : { + subject: impersonateEmail, + scopes: filteredScopes, + ...(errorCode && SA_DIAGNOSTIC_ERROR_CODES.has(errorCode) ? { errorCode } : {}), + }), + }) throw new ServiceAccountTokenError(response.status, description, errorCode) } - const tokenData = (await response.json()) as { access_token: string } + const tokenData = JSON.parse(response.body) as { access_token: string } return tokenData.access_token } @@ -660,11 +676,9 @@ async function resolveClientCredentialAccountToken( }) } -interface ServiceAccountTokenOptions { +interface ServiceAccountTokenOptions extends CredentialTokenResolutionOptions { scopes?: string[] impersonateEmail?: string - privacyMode?: 'selector' - githubRepositoryScope?: GitHubInstallationRepositoryScope } type ServiceAccountTokenResolver = ( @@ -725,7 +739,7 @@ const SERVICE_ACCOUNT_TOKEN_RESOLVERS: Record { if (!scopes?.length) { throw new Error('Scopes are required for service account credentials') @@ -733,6 +747,7 @@ const SERVICE_ACCOUNT_TOKEN_RESOLVERS: Record() + +beforeEach(() => { + vi.useFakeTimers() + fetchMock.mockReset() + vi.stubGlobal('fetch', fetchMock) +}) + +afterEach(() => { + vi.unstubAllGlobals() + vi.useRealTimers() +}) + +describe('Google service-account token transport', () => { + it.each([429, 500, 502, 503, 504])( + 'recovers a transient HTTP %s without changing the assertion', + async (status) => { + const first = Response.json({ error: 'temporarily_unavailable' }, { status }) + fetchMock + .mockResolvedValueOnce(first) + .mockResolvedValueOnce(Response.json({ access_token: 'token' })) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + const checked = expect(request).resolves.toMatchObject({ status: 200 }) + await vi.runAllTimersAsync() + await checked + expect(fetchMock).toHaveBeenCalledTimes(2) + expect(first.bodyUsed).toBe(true) + for (const [url, init] of fetchMock.mock.calls) { + expect(url).toBe(TOKEN_URI) + expect(init?.signal).toBeInstanceOf(AbortSignal) + expect(init?.redirect).toBe('error') + expect((init?.body as URLSearchParams).get('assertion')).toBe(ASSERTION) + } + } + ) + + it.each([400, 401, 403])('returns permanent HTTP %s without retrying', async (status) => { + const response = Response.json({ error: 'invalid_grant' }, { status }) + fetchMock.mockResolvedValueOnce(response) + await expect(exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION)).resolves.toEqual({ + ok: false, + status, + body: '{"error":"invalid_grant"}', + }) + expect(fetchMock).toHaveBeenCalledTimes(1) + expect(response.bodyUsed).toBe(true) + }) + + it('bounds repeated failures to three attempts and preserves the final error response', async () => { + fetchMock.mockImplementation(async () => + Response.json({ error: 'temporarily_unavailable' }, { status: 503 }) + ) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + await vi.runAllTimersAsync() + const response = await request + expect(fetchMock).toHaveBeenCalledTimes(3) + expect(response.status).toBe(503) + expect(JSON.parse(response.body)).toEqual({ error: 'temporarily_unavailable' }) + }) + + it('retries a network reset and preserves permanent transport failures', async () => { + const reset = new TypeError('fetch failed', { + cause: Object.assign(new Error('socket'), { code: 'ECONNRESET' }), + }) + fetchMock + .mockRejectedValueOnce(reset) + .mockResolvedValueOnce(Response.json({ access_token: 'token' })) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + const checked = expect(request).resolves.toMatchObject({ status: 200 }) + await vi.runAllTimersAsync() + await checked + expect(fetchMock).toHaveBeenCalledTimes(2) + fetchMock.mockReset() + const rejected = new TypeError('Invalid URL') + fetchMock.mockRejectedValueOnce(rejected) + await expect(exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION)).rejects.toBe(rejected) + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + + it('honors Retry-After and does not shorten waits exceeding the total budget', async () => { + fetchMock + .mockResolvedValueOnce(new Response(null, { status: 429, headers: { 'Retry-After': '5' } })) + .mockResolvedValueOnce(Response.json({ access_token: 'token' })) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + await vi.advanceTimersByTimeAsync(4999) + expect(fetchMock).toHaveBeenCalledTimes(1) + await vi.advanceTimersByTimeAsync(1) + await expect(request).resolves.toMatchObject({ status: 200 }) + fetchMock.mockReset() + const response = new Response(null, { status: 429, headers: { 'Retry-After': '60' } }) + fetchMock.mockResolvedValueOnce(response) + await expect(exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION)).resolves.toEqual({ + ok: false, + status: 429, + body: '', + }) + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + + it('preserves caller cancellation before requests and during retry waits', async () => { + const controller = new AbortController() + const reason = new DOMException('Cancelled', 'AbortError') + controller.abort(reason) + await expect( + exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION, controller.signal) + ).rejects.toBe(reason) + expect(fetchMock).not.toHaveBeenCalled() + const waiting = new AbortController() + fetchMock.mockResolvedValueOnce(new Response(null, { status: 503 })) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION, waiting.signal) + const checked = expect(request).rejects.toBe(reason) + await vi.advanceTimersByTimeAsync(1) + waiting.abort(reason) + await checked + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + + it('aborts a hung request when its total budget expires', async () => { + let requestSignal: AbortSignal | null | undefined + fetchMock.mockImplementation((_url, init) => { + requestSignal = init?.signal + return new Promise(() => undefined) + }) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + const checked = expect(request).rejects.toMatchObject({ name: 'TimeoutError' }) + await vi.advanceTimersByTimeAsync(30_000) + await checked + expect(requestSignal?.aborted).toBe(true) + expect(fetchMock).toHaveBeenCalledTimes(1) + }) + + it.each([200, 400, 503])( + 'bounds a stalled HTTP %s response body within the same deadline', + async (status) => { + let requestSignal: AbortSignal | null | undefined + fetchMock.mockImplementation(async (_url, init) => { + requestSignal = init?.signal + return new Response( + new ReadableStream({ + start(controller) { + requestSignal?.addEventListener( + 'abort', + () => controller.error(requestSignal?.reason), + { once: true } + ) + }, + }), + { status } + ) + }) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION) + const checked = expect(request).rejects.toMatchObject({ name: 'TimeoutError' }) + await vi.advanceTimersByTimeAsync(30_000) + await checked + expect(requestSignal?.aborted).toBe(true) + expect(fetchMock).toHaveBeenCalledTimes(1) + } + ) + + it('preserves the exact caller abort while reading an error body', async () => { + const caller = new AbortController() + const reason = new DOMException('Caller cancelled', 'AbortError') + fetchMock.mockImplementation( + async (_url, init) => + new Response( + new ReadableStream({ + start(controller) { + init?.signal?.addEventListener( + 'abort', + () => controller.error(new Error('body closed')), + { once: true } + ) + }, + }), + { status: 400 } + ) + ) + const request = exchangeGoogleServiceAccountJwt(TOKEN_URI, ASSERTION, caller.signal) + const checked = expect(request).rejects.toBe(reason) + await vi.advanceTimersByTimeAsync(1) + caller.abort(reason) + await checked + expect(fetchMock).toHaveBeenCalledTimes(1) + }) +}) diff --git a/apps/sim/lib/oauth/google-service-account-transport.ts b/apps/sim/lib/oauth/google-service-account-transport.ts new file mode 100644 index 00000000000..8cf93a77844 --- /dev/null +++ b/apps/sim/lib/oauth/google-service-account-transport.ts @@ -0,0 +1,79 @@ +import { parseRetryAfter } from '@sim/utils/retry' +import { + isRetryableError, + readBoundedHttpErrorPayload, + retryWithExponentialBackoff, +} from '@/lib/knowledge/documents/utils' + +const RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]) +const TOKEN_EXCHANGE_BUDGET_MS = 30_000 + +interface GoogleTokenExchangeResponse { + ok: boolean + status: number + body: string +} + +/** Keeps provider bodies and JWT assertions out of retry diagnostics. */ +class TokenExchangeRetryError extends Error { + readonly status: number + readonly retryAfterMs?: number + + constructor(response: Response) { + super(`Google service account token exchange failed (HTTP ${response.status})`) + this.status = response.status + this.retryAfterMs = + parseRetryAfter(response.headers.get('Retry-After'), Number.POSITIVE_INFINITY) ?? undefined + } +} + +/** Bounds token attempts and response consumption without changing final HTTP error classification. */ +export async function exchangeGoogleServiceAccountJwt( + tokenUri: string, + jwt: string, + signal?: AbortSignal +): Promise { + let lastResponse: GoogleTokenExchangeResponse | undefined + try { + return await retryWithExponentialBackoff( + async (attemptSignal) => { + lastResponse = undefined + const response = await fetch(tokenUri, { + method: 'POST', + redirect: 'error', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', + assertion: jwt, + }), + signal: attemptSignal, + }) + const payload = await readBoundedHttpErrorPayload(response) + attemptSignal.throwIfAborted() + if (response.ok && !payload.ok) + throw new Error('Google service account token response could not be read') + lastResponse = { + ok: response.ok, + status: response.status, + body: payload.ok ? payload.body : '', + } + if (RETRYABLE_STATUSES.has(response.status)) { + throw new TokenExchangeRetryError(response) + } + return lastResponse + }, + { + maxRetries: 2, + initialDelayMs: 1000, + maxDelayMs: 2000, + retryBudgetMs: TOKEN_EXCHANGE_BUDGET_MS, + signal, + retryCondition: (error) => + error instanceof TokenExchangeRetryError || isRetryableError(error), + } + ) + } catch (error) { + if (error instanceof TokenExchangeRetryError && lastResponse) return lastResponse + throw error + } +} diff --git a/packages/db/migrations/0356_google_company_user_progress.sql b/packages/db/migrations/0356_google_company_user_progress.sql new file mode 100644 index 00000000000..3bb4608f0dc --- /dev/null +++ b/packages/db/migrations/0356_google_company_user_progress.sql @@ -0,0 +1,27 @@ +CREATE TABLE "knowledge_connector_google_user" ( + "connector_id" text NOT NULL, + "user_id" text NOT NULL, + "generation_id" text NOT NULL, + "email" text NOT NULL, + "customer_id" text NOT NULL, + "cursor" text, + "status" text DEFAULT 'pending' NOT NULL, + "attempts" integer DEFAULT 0 NOT NULL, + "retry_at" timestamp DEFAULT now() NOT NULL, + "last_served_at" timestamp, + "failure" jsonb, + "permission_cursor" text, + "permission_attempts" integer DEFAULT 0 NOT NULL, + "permission_retry_at" timestamp NOT NULL, + "permission_last_served_at" timestamp, + "permission_started_at" timestamp, + "permission_failure" jsonb, + CONSTRAINT "kcgu_pk" PRIMARY KEY("connector_id","user_id"), + CONSTRAINT "kcgu_status_check" CHECK ("knowledge_connector_google_user"."status" IN ('pending', 'complete', 'blocked')), + CONSTRAINT "kcgu_cursor_check" CHECK (("knowledge_connector_google_user"."cursor" IS NULL OR octet_length("knowledge_connector_google_user"."cursor") <= 393216) AND ("knowledge_connector_google_user"."permission_cursor" IS NULL OR octet_length("knowledge_connector_google_user"."permission_cursor") <= 393216)), + CONSTRAINT "kcgu_attempts_check" CHECK ("knowledge_connector_google_user"."attempts" >= 0 AND "knowledge_connector_google_user"."permission_attempts" >= 0) +); +--> statement-breakpoint +ALTER TABLE "knowledge_connector_google_user" ADD CONSTRAINT "knowledge_connector_google_user_connector_id_knowledge_connector_id_fk" FOREIGN KEY ("connector_id") REFERENCES "public"."knowledge_connector"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "kcgu_content_due_idx" ON "knowledge_connector_google_user" USING btree ("connector_id","generation_id","status","retry_at","last_served_at");--> statement-breakpoint +CREATE INDEX "kcgu_permission_due_idx" ON "knowledge_connector_google_user" USING btree ("connector_id","generation_id","permission_retry_at","permission_last_served_at"); \ No newline at end of file diff --git a/packages/db/migrations/meta/0356_snapshot.json b/packages/db/migrations/meta/0356_snapshot.json new file mode 100644 index 00000000000..2d5a68f21f9 --- /dev/null +++ b/packages/db/migrations/meta/0356_snapshot.json @@ -0,0 +1,27507 @@ +{ + "id": "6cbb7342-0685-49b4-b9ef-637e9a092ac3", + "prevId": "ae380daf-a120-4f9d-8908-caa1c0980f76", + "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_google_user": { + "name": "knowledge_connector_google_user", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_id": { + "name": "generation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "retry_at": { + "name": "retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_served_at": { + "name": "last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure": { + "name": "failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "permission_cursor": { + "name": "permission_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_attempts": { + "name": "permission_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "permission_retry_at": { + "name": "permission_retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permission_last_served_at": { + "name": "permission_last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_started_at": { + "name": "permission_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_failure": { + "name": "permission_failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcgu_content_due_idx": { + "name": "kcgu_content_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcgu_permission_due_idx": { + "name": "kcgu_permission_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_google_user_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_google_user_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_google_user", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kcgu_pk": { + "name": "kcgu_pk", + "columns": ["connector_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcgu_status_check": { + "name": "kcgu_status_check", + "value": "\"knowledge_connector_google_user\".\"status\" IN ('pending', 'complete', 'blocked')" + }, + "kcgu_cursor_check": { + "name": "kcgu_cursor_check", + "value": "(\"knowledge_connector_google_user\".\"cursor\" IS NULL OR octet_length(\"knowledge_connector_google_user\".\"cursor\") <= 393216) AND (\"knowledge_connector_google_user\".\"permission_cursor\" IS NULL OR octet_length(\"knowledge_connector_google_user\".\"permission_cursor\") <= 393216)" + }, + "kcgu_attempts_check": { + "name": "kcgu_attempts_check", + "value": "\"knowledge_connector_google_user\".\"attempts\" >= 0 AND \"knowledge_connector_google_user\".\"permission_attempts\" >= 0" + } + }, + "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_activity_idx": { + "name": "workflow_execution_logs_workspace_activity_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "total_duration_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_deadline_idx": { + "name": "workflow_execution_logs_running_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_started_at_idx": { + "name": "workflow_execution_logs_redacting_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'redacting'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_redacting_deadline_idx": { + "name": "workflow_execution_logs_redacting_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "mounted_secrets": { + "name": "mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_operation", + "columnsFrom": ["deployment_operation_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_secret_scope": { + "name": "inbox_secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "inbox_mounted_secrets": { + "name": "inbox_mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "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 dd7fe8327b2..b011c96b2b6 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2486,6 +2486,13 @@ "when": 1789609714073, "tag": "0355_workflow_activity_covering_index", "breakpoints": true + }, + { + "idx": 356, + "version": "7", + "when": 1789668694580, + "tag": "0356_google_company_user_progress", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 1e43fea47af..fc628747528 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -5698,6 +5698,60 @@ export const knowledgeConnector = pgTable( }) ) +/** Bounded independent Google Workspace user continuations, committed with their owning listing checkpoint. */ +export const knowledgeConnectorGoogleUser = pgTable( + 'knowledge_connector_google_user', + { + connectorId: text('connector_id') + .notNull() + .references(() => knowledgeConnector.id, { onDelete: 'cascade' }), + userId: text('user_id').notNull(), + generationId: text('generation_id').notNull(), + email: text('email').notNull(), + customerId: text('customer_id').notNull(), + cursor: text('cursor'), + status: text('status').notNull().default('pending'), + attempts: integer('attempts').notNull().default(0), + retryAt: timestamp('retry_at').notNull().defaultNow(), + lastServedAt: timestamp('last_served_at'), + failure: jsonb('failure').$type>(), + permissionCursor: text('permission_cursor'), + permissionAttempts: integer('permission_attempts').notNull().default(0), + permissionRetryAt: timestamp('permission_retry_at').notNull(), + permissionLastServedAt: timestamp('permission_last_served_at'), + permissionStartedAt: timestamp('permission_started_at'), + permissionFailure: jsonb('permission_failure').$type>(), + }, + (table) => ({ + pk: primaryKey({ name: 'kcgu_pk', columns: [table.connectorId, table.userId] }), + contentDueIdx: index('kcgu_content_due_idx').on( + table.connectorId, + table.generationId, + table.status, + table.retryAt, + table.lastServedAt + ), + permissionDueIdx: index('kcgu_permission_due_idx').on( + table.connectorId, + table.generationId, + table.permissionRetryAt, + table.permissionLastServedAt + ), + statusCheck: check( + 'kcgu_status_check', + sql`${table.status} IN ('pending', 'complete', 'blocked')` + ), + cursorCheck: check( + 'kcgu_cursor_check', + sql`(${table.cursor} IS NULL OR octet_length(${table.cursor}) <= 393216) AND (${table.permissionCursor} IS NULL OR octet_length(${table.permissionCursor}) <= 393216)` + ), + attemptsCheck: check( + 'kcgu_attempts_check', + sql`${table.attempts} >= 0 AND ${table.permissionAttempts} >= 0` + ), + }) +) + /** Private provider configuration; metadata reads never materialize the larger normalized payload. */ export const knowledgeConnectorPermissionSnapshot = pgTable( 'knowledge_connector_permission_snapshot', diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts index 625dbab0d29..e246a16cebe 100644 --- a/packages/testing/src/mocks/schema.mock.ts +++ b/packages/testing/src/mocks/schema.mock.ts @@ -1740,6 +1740,25 @@ export const schemaMock = { subjectToken: 'knowledgeExternalGroupMember.subjectToken', createdAt: 'knowledgeExternalGroupMember.createdAt', }, + knowledgeConnectorGoogleUser: { + connectorId: 'knowledgeConnectorGoogleUser.connectorId', + userId: 'knowledgeConnectorGoogleUser.userId', + generationId: 'knowledgeConnectorGoogleUser.generationId', + email: 'knowledgeConnectorGoogleUser.email', + customerId: 'knowledgeConnectorGoogleUser.customerId', + cursor: 'knowledgeConnectorGoogleUser.cursor', + status: 'knowledgeConnectorGoogleUser.status', + attempts: 'knowledgeConnectorGoogleUser.attempts', + retryAt: 'knowledgeConnectorGoogleUser.retryAt', + lastServedAt: 'knowledgeConnectorGoogleUser.lastServedAt', + failure: 'knowledgeConnectorGoogleUser.failure', + permissionCursor: 'knowledgeConnectorGoogleUser.permissionCursor', + permissionAttempts: 'knowledgeConnectorGoogleUser.permissionAttempts', + permissionRetryAt: 'knowledgeConnectorGoogleUser.permissionRetryAt', + permissionLastServedAt: 'knowledgeConnectorGoogleUser.permissionLastServedAt', + permissionStartedAt: 'knowledgeConnectorGoogleUser.permissionStartedAt', + permissionFailure: 'knowledgeConnectorGoogleUser.permissionFailure', + }, knowledgeConnectorMember: { id: 'knowledgeConnectorMember.id', workspaceId: 'knowledgeConnectorMember.workspaceId', From bd64ce30ef35e35f297a124517cac96343720013 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 17 Sep 2026 12:01:33 -0700 Subject: [PATCH 2/4] fix(search): harden crawl completion and recovery --- .../google-company-work.integration.ts | 114 +++++++++- .../listing-continuation.integration.ts | 210 ++++++++++++++++-- .../stored-document-recovery.integration.ts | 87 ++++++++ .../google-company-scheduler.test.ts | 62 +++++- .../connectors/google-company-store.ts | 25 ++- .../connectors/sync-content-pass.test.ts | 4 +- .../knowledge/connectors/sync-content-pass.ts | 4 +- .../documents/ocr-source-validation.test.ts | 21 ++ .../documents/ocr-source-validation.ts | 2 +- .../documents/processing-recovery.ts | 37 +-- 10 files changed, 508 insertions(+), 58 deletions(-) diff --git a/apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts b/apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts index ccb19da1da8..6906dcbef7e 100644 --- a/apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts @@ -7,9 +7,10 @@ import { user, workspace, } from '@sim/db/schema' -import { eq, inArray } from 'drizzle-orm' -import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { and, eq, inArray } from 'drizzle-orm' +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { seedKnowledgeAclFixture } from '@/lib/knowledge/__integration__/seed-source-access-fixture' +import type { GoogleCompanyWorkUpdate } from '@/lib/knowledge/connectors/google-company-scheduler' import { commitGoogleCompanyWork, googleCompanyWorkStore, @@ -35,7 +36,7 @@ describe('Google company user checkpoint storage', () => { startedAt: generationStartedAt, }) - beforeAll(async () => { + beforeEach(async () => { owner = await seedKnowledgeAclFixture(undefined, { connectorType: 'google_drive' }) await db.transaction(async (tx) => { await commitGoogleCompanyWork( @@ -51,12 +52,13 @@ describe('Google company user checkpoint storage', () => { .where(eq(knowledgeConnector.id, owner.connectorId)) }) }) - afterAll(async () => { + afterEach(async () => { + vi.unstubAllEnvs() await db.delete(workspace).where(eq(workspace.id, owner.workspaceId)) await db.delete(organization).where(eq(organization.id, owner.organizationId)) await db.delete(user).where(inArray(user.id, [owner.aliceId, owner.bobId])) - await db.$client.end() }) + afterAll(() => db.$client.end()) it('rolls back user progress and the owning checkpoint together', async () => { const store = googleCompanyWorkStore(owner.connectorId, generationId) @@ -208,6 +210,23 @@ describe('Google company user checkpoint storage', () => { }) it('does not rescan completed manual work or use its past retry time to wake blocked work', async () => { + await commitGoogleCompanyWork( + db, + owner.connectorId, + generationId, + { + update: { + userId: 'first', + kind: 'content', + cursor: 'blocked-user-page', + completed: false, + retryAt: new Date(Date.now() + 60 * 60_000), + attempts: 1, + failure: { scope: first.user.email, operation: 'drive.files.list', reasons: [] }, + }, + }, + generationStartedAt + ) await commitGoogleCompanyWork( db, owner.connectorId, @@ -252,7 +271,80 @@ describe('Google company user checkpoint storage', () => { expect((await automatic.remaining()).count).toBe(0) }) - it('resets both provider continuations on a new configuration generation and cascades on connector deletion', async () => { + it.each(['continuation', 'failure'] as const)( + 'retains a deferred permission %s after all content completes without keeping future refreshes open', + async (pending) => { + vi.stubEnv('TZ', 'Pacific/Honolulu') + const now = new Date() + const retryAt = new Date(now.getTime() + 30 * 60_000) + const nextPeriodicRefresh = new Date(now.getTime() + 12 * 60 * 60_000) + const update = ( + userId: string, + kind: GoogleCompanyWorkUpdate['kind'], + changes: Partial = {} + ) => + commitGoogleCompanyWork( + db, + owner.connectorId, + generationId, + { + update: { + userId, + kind, + cursor: null, + completed: true, + retryAt: nextPeriodicRefresh, + attempts: 0, + failure: null, + permissionStartedAt: null, + ...changes, + }, + }, + generationStartedAt + ) + for (const userId of ['first', 'second']) { + await update(userId, 'permissions') + await update(userId, 'content', { retryAt: new Date(now.getTime() - 60_000) }) + } + await update('first', 'permissions', { + cursor: pending === 'continuation' ? 'permission-page-9' : null, + completed: false, + retryAt, + attempts: pending === 'failure' ? 1 : 0, + failure: + pending === 'failure' + ? { + scope: first.user.email, + operation: 'drive.files.list', + status: 403, + reasons: [], + } + : null, + }) + const automatic = googleCompanyWorkStore(owner.connectorId, generationId) + const manual = googleCompanyWorkStore(owner.connectorId, generationId, false) + expect(await manual.remaining()).toMatchObject({ count: 1, retryAt }) + expect(await manual.next('content', now)).toBeNull() + expect(await automatic.next('content', now)).not.toBeNull() + expect(await automatic.next('permissions', now)).toBeNull() + expect((await automatic.next('permissions', retryAt))?.user.id).toBe('first') + for (const userId of ['first', 'second']) { + await update(userId, 'content', { retryAt: new Date(now.getTime() + 2 * 60 * 60_000) }) + } + expect(await automatic.remaining()).toMatchObject({ count: 1, retryAt }) + await update('first', 'permissions') + expect(await automatic.remaining()).toEqual({ count: 0, retryAt: null }) + expect(await manual.remaining()).toEqual({ count: 0, retryAt: null }) + expect(await automatic.next('content', new Date(now.getTime() + 3 * 60 * 60_000))).toBeNull() + } + ) + + it('resets both provider continuations while preserving due permissions, and cascades on connector deletion', async () => { + const permissionDueAt = new Date(Date.now() - 60_000) + await db + .update(knowledgeConnectorGoogleUser) + .set({ permissionRetryAt: permissionDueAt }) + .where(eq(knowledgeConnectorGoogleUser.connectorId, owner.connectorId)) await db.transaction(async (tx) => { await commitGoogleCompanyWork( tx, @@ -285,6 +377,16 @@ describe('Google company user checkpoint storage', () => { cursor: undefined, permissionStartedAt: undefined, }) + const [progress] = await db + .select({ permissionRetryAt: knowledgeConnectorGoogleUser.permissionRetryAt }) + .from(knowledgeConnectorGoogleUser) + .where( + and( + eq(knowledgeConnectorGoogleUser.connectorId, owner.connectorId), + eq(knowledgeConnectorGoogleUser.userId, 'first') + ) + ) + expect(progress.permissionRetryAt).toEqual(permissionDueAt) await db.delete(knowledgeConnector).where(eq(knowledgeConnector.id, owner.connectorId)) expect( await db diff --git a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts index 51c3efce155..78433732ff9 100644 --- a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts @@ -11,6 +11,7 @@ import { document, embedding, knowledgeConnector, + knowledgeConnectorGoogleUser, knowledgeConnectorMember, knowledgeConnectorSyncLog, knowledgeDocumentObservation, @@ -99,7 +100,7 @@ import { addDocument, persistDocumentAcls } from '@/lib/knowledge/connectors/syn import * as documentService from '@/lib/knowledge/documents/service' import { downloadFileFromUrl } from '@/lib/uploads/utils/file-utils.server' import { CONNECTOR_REGISTRY } from '@/connectors/registry.server' -import type { ExternalDocument, SyncResult } from '@/connectors/types' +import type { ConnectorConfig, ExternalDocument, SyncResult } from '@/connectors/types' const body = 'Durable source pagination preserves the checkpoint, indexes each document once, and applies access revocation only after an authoritative complete listing.' @@ -260,10 +261,12 @@ describe('durable source and member cycles in PostgreSQL', () => { expect(changed.contentHash).toBe('hash-refresh-changed') expect(changed.acl).toEqual(ownerAcl) expect(changed.aclVerifiedAt!.getTime()).toBeGreaterThan(oldVerifiedAt.getTime()) + expect(changed.sourceSeenAt).toEqual(oldVerifiedAt) const failed = rows.find((row) => row.externalId === 'refresh-failed')! expect(failed.acl).toEqual([]) expect(failed.aclVerifiedAt).toBeNull() expect(failed.processingStatus).toBe('failed') + expect(failed.sourceSeenAt).toEqual(oldVerifiedAt) expect(rows.find((row) => row.externalId === 'refresh-absent')!.aclVerifiedAt).toEqual( oldVerifiedAt ) @@ -276,15 +279,172 @@ describe('durable source and member cycles in PostgreSQL', () => { }) }) + it('preserves a permission-repaired document through the remaining content crawl and EOF reconciliation', async () => { + const connectorId = generateId() + const runId = generateId() + const fingerprint = listingFingerprint({ source: 'permission-refresh-before-eof' }) + const checkpoint = { + ...beginListingCheckpoint({ + fingerprint, + generationId: generateId(), + startedAt: new Date(Date.now() - 13 * 60 * 60 * 1000), + }), + cursor: 'permissions', + listedCount: 1, + } + const seenAt = new Date(checkpoint.startedAt) + const oldVerifiedAt = new Date(seenAt.getTime() - 60 * 60 * 1000) + const ownerAcl = [`u:${ids.aliceId}@fixture.test`] + await db.insert(knowledgeConnector).values({ + id: connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + connectorType: 'google_drive', + status: 'syncing', + syncLockToken: runId, + accessMode: 'admin', + sourceConfig: {}, + listingCheckpoint: checkpoint, + }) + await db.insert(document).values( + ['repaired', 'remaining', 'absent'].map((name) => ({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + connectorId, + externalId: `permission-eof-${name}`, + filename: name, + fileUrl: '', + storageKey: `kb/permission-eof-${name}.txt`, + fileSize: 10, + mimeType: 'text/plain', + contentHash: name === 'repaired' ? 'old-body' : `hash-permission-eof-${name}`, + processingStatus: 'completed', + acl: ownerAcl, + aclVerifiedAt: oldVerifiedAt, + sourceSeenAt: name === 'repaired' ? seenAt : oldVerifiedAt, + })) + ) + const listDocuments = vi.fn( + async (_token, _source, cursor) => + cursor === 'permissions' + ? { + documents: [ + { + ...sourceDoc('permission-eof-repaired'), + content: '', + contentDeferred: true, + acl: ownerAcl, + }, + ], + hasMore: true, + nextCursor: 'remaining', + permissionsOnly: true, + resumeAt: new Date(Date.now() + 60_000).toISOString(), + } + : { + documents: [{ ...sourceDoc('permission-eof-remaining'), acl: ownerAcl }], + hasMore: false, + } + ) + const getDocument = vi.fn(async (externalId: string) => { + const [duringHydration] = await db + .select({ acl: document.acl, aclVerifiedAt: document.aclVerifiedAt }) + .from(document) + .where(and(eq(document.connectorId, connectorId), eq(document.externalId, externalId))) + expect(duringHydration).toEqual({ acl: [], aclVerifiedAt: null }) + return sourceDoc(externalId) + }) + const run = async (leaseId: string) => { + const [connector] = await db + .select() + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, connectorId)) + const stats = result() + const pass = await runConnectorContentPass({ + connectorId, + connector, + connectorConfig: { ...CONNECTOR_REGISTRY.google_drive, listDocuments }, + sourceConfig: {}, + syncContext: {}, + kbOwner: { userId: ids.aliceId, workspaceId: ids.workspaceId }, + billingAttribution: billing, + result: stats, + lease: createContentSyncLease(connectorId, leaseId), + leaseKind: 'content', + runId: leaseId, + fingerprint, + documentAccess: 'admin', + getAccessToken: async () => 'fixture', + hydration: { getDocument }, + forceRehydrate: false, + deadlineAt: Date.now() + 60_000, + onPage: async (verified) => { + await persistDocumentAcls( + connectorId, + new Map(verified.map((item) => [item.externalId, item.acl!])) + ) + return { permissionsIncomplete: false } + }, + }) + return { pass, stats } + } + const first = await run(runId) + expect(first.pass).toMatchObject({ + complete: false, + checkpoint: { cursor: 'remaining', listedCount: 1, unsafe: false }, + }) + expect(first.stats).toMatchObject({ docsUpdated: 1, docsDeleted: 0 }) + const nextLease = generateId() + await db + .update(knowledgeConnector) + .set({ syncLockToken: nextLease }) + .where(eq(knowledgeConnector.id, connectorId)) + const final = await run(nextLease) + expect(final.pass).toMatchObject({ + complete: true, + holdNotice: null, + checkpoint: { generationId: checkpoint.generationId, listedCount: 2, unsafe: false }, + }) + expect(getDocument).toHaveBeenCalledOnce() + expect(listDocuments.mock.calls.map((call) => call[2])).toEqual(['permissions', 'remaining']) + const rows = await db.select().from(document).where(eq(document.connectorId, connectorId)) + const repaired = rows.find((row) => row.externalId === 'permission-eof-repaired')! + expect(repaired).toMatchObject({ + contentHash: 'hash-permission-eof-repaired', + processingStatus: 'completed', + acl: ownerAcl, + sourceSeenAt: seenAt, + deletedAt: null, + }) + expect(repaired.aclVerifiedAt!.getTime()).toBeGreaterThan(oldVerifiedAt.getTime()) + expect(rows.find((row) => row.externalId === 'permission-eof-absent')).toMatchObject({ + acl: [], + deletedAt: expect.any(Date), + }) + expect(final.stats).toMatchObject({ docsUnchanged: 1, docsDeleted: 1 }) + }) + it('repairs a changed Google document already seen in an unfinished company generation', async () => { const connectorId = generateId() const runId = generateId() const fingerprint = listingFingerprint({ source: 'google-content-rescan' }) - const checkpoint = beginListingCheckpoint({ - fingerprint, - generationId: 'rescan-generation', - startedAt: new Date(Date.now() - 13 * 60 * 60 * 1000), - }) + const checkpoint = { + ...beginListingCheckpoint({ + fingerprint, + generationId: 'rescan-generation', + startedAt: new Date(Date.now() - 13 * 60 * 60 * 1000), + }), + cursor: `google-company-work:v2:${Buffer.from( + JSON.stringify({ + directoryComplete: true, + directoryRefreshAt: new Date(Date.now() + 60 * 60 * 1000).toISOString(), + phase: 'users', + revision: 5, + permissionTurn: false, + active: { userId: 'google-user', kind: 'content' }, + }) + ).toString('base64url')}`, + listedCount: 1, + } await db.insert(knowledgeConnector).values({ id: connectorId, knowledgeBaseId: ids.knowledgeBaseId, @@ -310,23 +470,29 @@ describe('durable source and member cycles in PostgreSQL', () => { processingStatus: 'completed', acl: [], }) - fixture.directory.mockResolvedValue({ - users: [ - { - id: 'google-user', - email: 'google-user@fixture.test', - customerId: 'customer', - active: true, - }, - ], + await db.insert(knowledgeConnectorGoogleUser).values({ + connectorId, + generationId: checkpoint.generationId, + userId: 'google-user', + email: 'google-user@fixture.test', + customerId: 'customer', + cursor: 'saved-content-page-91', + lastServedAt: new Date(checkpoint.startedAt), + permissionRetryAt: new Date(Date.now() + 12 * 60 * 60 * 1000), }) + fixture.directory.mockClear() const listed = { ...sourceDoc('rescan-changed'), content: '', contentDeferred: true, acl: ['u:google-user@fixture.test'], } - const listDocuments = vi.fn(async () => ({ documents: [listed], hasMore: false })) + const listDocuments = vi.fn( + async (_token, _source, cursor) => { + expect(cursor).toBe('saved-content-page-91') + return { documents: [listed], hasMore: false } + } + ) const getDocument = vi.fn(async () => sourceDoc('rescan-changed')) const [connector] = await db .select() @@ -363,8 +529,20 @@ describe('durable source and member cycles in PostgreSQL', () => { }, }) expect(pass.complete).toBe(true) + expect(pass.checkpoint.generationId).toBe(checkpoint.generationId) + expect(fixture.directory).not.toHaveBeenCalled() + expect(listDocuments).toHaveBeenCalledOnce() expect(getDocument).toHaveBeenCalledOnce() expect(syncResult.docsUpdated).toBe(1) + const [userProgress] = await db + .select() + .from(knowledgeConnectorGoogleUser) + .where(eq(knowledgeConnectorGoogleUser.connectorId, connectorId)) + expect(userProgress).toMatchObject({ + generationId: checkpoint.generationId, + cursor: null, + status: 'complete', + }) const [stored] = await db.select().from(document).where(eq(document.connectorId, connectorId)) expect(stored.contentHash).toBe('hash-rescan-changed') expect(stored.aclVerifiedAt!.getTime()).toBeGreaterThan( diff --git a/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts b/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts index 4bff77a42e6..8d3a3f45a09 100644 --- a/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts @@ -385,6 +385,93 @@ describe('independent recovery of retained connector documents', () => { } ) + it('recovers a sibling connector when the oldest candidate batch belongs to a locked connector', async () => { + const blocked = await seed() + const file = await failedFile(blocked) + const [original] = await db.select().from(document).where(eq(document.id, file.documentId)) + await db.insert(document).values( + Array.from({ length: DOCUMENT_RECOVERY_BATCH_SIZE - 1 }, () => ({ + ...original, + id: generateId(), + externalId: generateId(), + secretProvenanceVersion: null, + })) + ) + const healthy = { ...blocked, connectorId: generateId(), lockId: generateId() } + await db.insert(knowledgeConnector).values({ + id: healthy.connectorId, + knowledgeBaseId: healthy.knowledgeBaseId, + connectorType: 'google_drive', + sourceConfig: {}, + accessMode: 'admin', + status: 'syncing', + syncLockToken: healthy.lockId, + }) + const healthyFile = await failedFile(healthy) + await db + .update(document) + .set({ uploadedAt: new Date(original.uploadedAt.getTime() + 1) }) + .where(eq(document.id, healthyFile.documentId)) + + let release!: () => void + let acquired!: () => void + const released = new Promise((resolve) => { + release = resolve + }) + const locked = new Promise((resolve) => { + acquired = resolve + }) + const holder = db.transaction(async (tx) => { + await tx + .select({ id: knowledgeBase.id }) + .from(knowledgeBase) + .where(eq(knowledgeBase.id, blocked.knowledgeBaseId)) + .for('share') + await tx + .select({ id: knowledgeConnector.id }) + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, blocked.connectorId)) + .for('update') + acquired() + await released + }) + await locked + try { + expect(await recoverKnowledgeDocumentProcessing()).toBe(1) + const events = await eventsFor(blocked) + expect(events).toHaveLength(1) + expect(events[0].payload).toMatchObject({ documentId: healthyFile.documentId }) + const [admitted] = await db + .select() + .from(document) + .where(eq(document.id, healthyFile.documentId)) + expect(admitted.processingAttempts).toBe(2) + expect(admitted.processingQueueToken).toBe(events[0].id) + const untouched = await db + .select() + .from(document) + .where(eq(document.connectorId, blocked.connectorId)) + expect(untouched).toHaveLength(DOCUMENT_RECOVERY_BATCH_SIZE) + for (const row of untouched) { + expect(row).toMatchObject({ + processingStatus: original.processingStatus, + processingAttempts: original.processingAttempts, + processingQueueToken: original.processingQueueToken, + processingQueuedAt: original.processingQueuedAt, + processingError: original.processingError, + processingRecoveryAfter: original.processingRecoveryAfter, + }) + } + } finally { + release() + await holder + await db + .update(knowledgeConnector) + .set({ status: 'paused' }) + .where(inArray(knowledgeConnector.id, [blocked.connectorId, healthy.connectorId])) + } + }) + it('recovers another document while an index transaction holds the same KB foreign-key lock', async () => { const ids = await seed() const file = await failedFile(ids) diff --git a/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts b/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts index 256611f5b73..f637d713480 100644 --- a/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts +++ b/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts @@ -46,6 +46,8 @@ function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { let served = 0 const rows = new Map() const key = (id: string, kind: GoogleCompanyWorkKind) => `${id}:${kind}` + const incomplete = (row: FakeWork) => + row.kind === 'content' ? !row.complete : Boolean(row.cursor || row.failure) const store: GoogleCompanyWorkStore = { get: async (id, kind) => rows.get(key(id, kind)) ?? null, next: async (kind, now) => @@ -56,17 +58,21 @@ function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { row.retryAt <= now && (kind !== 'content' || !row.complete || - (syncIntervalMinutes > 0 && - [...rows.values()].some((item) => item.kind === 'content' && !item.complete))) && + (syncIntervalMinutes > 0 && [...rows.values()].some(incomplete))) && (kind === 'content' || (rows.get(key(row.user.id, 'content'))?.served ?? 0) > 0) ) .sort((a, b) => a.served - b.served || a.user.id.localeCompare(b.user.id))[0] ?? null, remaining: async () => { - const remaining = [...rows.values()].filter((row) => row.kind === 'content' && !row.complete) + const remaining = [...rows.values()].filter(incomplete) + const retryable = [...rows.values()].filter( + (row) => incomplete(row) || (row.kind === 'content' && syncIntervalMinutes > 0) + ) const failed = [...rows.values()].filter((row) => row.failure) return { - count: remaining.length, - retryAt: remaining.sort((a, b) => +a.retryAt - +b.retryAt)[0]?.retryAt ?? null, + count: new Set(remaining.map((row) => row.user.id)).size, + retryAt: remaining.length + ? (retryable.sort((a, b) => +a.retryAt - +b.retryAt)[0]?.retryAt ?? null) + : null, ...(failed.length ? { failures: { @@ -265,18 +271,18 @@ describe('durable Google company user scheduling', () => { expect(f.saved().resumeAt).toBe('2026-09-17T00:05:00.000Z') }) - it('adopts a production cursor without resetting the active provider page', async () => { + it('adopts a legacy cursor without resetting the active provider page', async () => { mocks.directory.mockResolvedValue({ users: [user('a'), user('z')] }) const f = fixture() const legacy = `google-workspace:v1:${Buffer.from(JSON.stringify({ provider: 'google_calendar', users: [user('a')], providerCursor: 'existing-page-91', nextUsersPageToken: 'old-directory' })).toString('base64url')}` - f.setSaved({ ...f.saved(), cursor: legacy, listedCount: 6224 }) + f.setSaved({ ...f.saved(), cursor: legacy, listedCount: 10 }) await f.step(3) expect(f.list.mock.calls[0]?.[2]).toContain('google-workspace:v1:') const resume = JSON.parse( Buffer.from(f.list.mock.calls[0]![2]!.split(':v1:')[1], 'base64url').toString() ) expect(resume.providerCursor).toBe('existing-page-91') - expect(f.saved().listedCount).toBe(6225) + expect(f.saved().listedCount).toBe(11) expect(mocks.directory.mock.calls[0]?.[1]).toBeUndefined() }) @@ -427,6 +433,46 @@ describe('durable Google company user scheduling', () => { expect(f.rows.get('a:content')?.retryAt).toEqual(new Date('2026-09-17T00:15:00Z')) }) + it.each([0, 60])( + 'waits for deferred permissions at cadence %i, then reaches EOF without waiting for periodic refresh', + async (cadence) => { + mocks.directory.mockResolvedValue({ users: [user('a')] }) + const f = fixture('google_calendar', cadence) + await f.step(2) + const content = structuredClone(f.rows.get('a:content')) + const permission = f.rows.get('a:permissions')! + permission.cursor = 'permission-page-2' + permission.retryAt = new Date('2026-09-17T00:00:00Z') + f.list.mockRejectedValueOnce(new GoogleApiError('calendar.events.list', 403, [], false)) + await f.step(2) + expect(f.saved()).toMatchObject({ + generationId: 'generation', + complete: false, + resumeAt: '2026-09-17T00:05:00.000Z', + listingFailures: { count: 1 }, + }) + expect(permission.cursor).toBe('permission-page-2') + const callsBeforeRetry = f.list.mock.calls.length + f.restart() + await f.step() + expect(f.list).toHaveBeenCalledTimes(callsBeforeRetry) + expect(f.saved().complete).toBe(false) + f.advance(5 * 60_000) + f.restart() + await f.step(2) + expect(f.list.mock.calls.at(-1)?.[2]).toBe('permission-page-2') + expect(f.rows.get('a:content')).toEqual(content) + expect(f.saved()).toMatchObject({ + generationId: 'generation', + complete: true, + resumeAt: null, + listingFailures: null, + }) + expect(permission.retryAt).toEqual(new Date('2026-09-17T12:05:00Z')) + expect(mocks.directory).toHaveBeenCalledTimes(1) + } + ) + it('keeps completed manual users complete while unfinished work and permission refresh continue', async () => { mocks.directory.mockResolvedValue({ users: [user('a'), user('b')] }) const f = fixture('gmail', 0) diff --git a/apps/sim/lib/knowledge/connectors/google-company-store.ts b/apps/sim/lib/knowledge/connectors/google-company-store.ts index 5ea1f37971f..4ec644e5ead 100644 --- a/apps/sim/lib/knowledge/connectors/google-company-store.ts +++ b/apps/sim/lib/knowledge/connectors/google-company-store.ts @@ -32,6 +32,12 @@ export function googleCompanyWorkStore( rescanCompleted = true ): GoogleCompanyWorkStore { const scope = and(eq(work.connectorId, connectorId), eq(work.generationId, generationId)) + const contentIncomplete = ne(work.status, 'complete') + const permissionsIncomplete = or( + isNotNull(work.permissionCursor), + isNotNull(work.permissionFailure) + ) + const incomplete = or(contentIncomplete, permissionsIncomplete) return { async get(userId, kind) { const [row] = await db @@ -43,13 +49,12 @@ export function googleCompanyWorkStore( }, async next(kind, now) { /** A completed sweep must reach EOF instead of starting another overdue user sweep. */ - const incomplete = ne(work.status, 'complete') const contentEligible = rescanCompleted ? or( - incomplete, + contentIncomplete, exists(db.select({ id: work.userId }).from(work).where(and(scope, incomplete))) ) - : incomplete + : contentIncomplete const [row] = await db .select() .from(work) @@ -71,13 +76,12 @@ export function googleCompanyWorkStore( async remaining() { const [counts] = await db .select({ - count: sql`count(*) FILTER (WHERE ${work.status} <> 'complete')::int`, + count: sql`count(*) FILTER (WHERE ${incomplete})::int`, failures: sql`count(*) FILTER (WHERE ${work.failure} IS NOT NULL OR ${work.permissionFailure} IS NOT NULL)::int`, - retryAt: rescanCompleted - ? sql`min(${work.retryAt})::text` - : sql< - string | null - >`min(${work.retryAt}) FILTER (WHERE ${work.status} <> 'complete')::text`, + retryAt: sql`LEAST( + min(${work.retryAt}) FILTER (WHERE ${rescanCompleted ? sql`true` : contentIncomplete}), + min(${work.permissionRetryAt}) FILTER (WHERE ${permissionsIncomplete}) + )`.mapWith(work.retryAt), }) .from(work) .where(scope) @@ -91,7 +95,7 @@ export function googleCompanyWorkStore( : [] return { count: counts?.count ?? 0, - retryAt: counts?.retryAt ? new Date(counts.retryAt) : null, + retryAt: counts?.count ? counts.retryAt : null, ...(counts?.failures ? { failures: listingFailuresSchema.parse({ @@ -129,6 +133,7 @@ export async function commitGoogleCompanyWork( permissionRetryAt: new Date(generationStartedAt.getTime() + PERMISSION_REFRESH_MS), })) ) + /** Content restarts must not postpone the earlier deadline for refreshing stored permissions. */ .onConflictDoUpdate({ target: [work.connectorId, work.userId], set: { 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 ad981f4657c..5ab9a1ee5d1 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.test.ts @@ -654,8 +654,8 @@ describe('permission refresh through the shared content pass', () => { acl: [], aclRequirements: [], aclVerifiedAt: null, - sourceSeenAt: null, }) + expect(dbChainMockFns.set.mock.calls.some(([value]) => 'sourceSeenAt' in value)).toBe(false) expect(result.docsUpdated).toBe(1) expect(mocks.onPage).toHaveBeenCalledWith( [expect.objectContaining({ contentHash: current.contentHash })], @@ -679,8 +679,8 @@ describe('permission refresh through the shared content pass', () => { acl: [], aclRequirements: [], aclVerifiedAt: null, - sourceSeenAt: null, }) + expect(dbChainMockFns.set.mock.calls.some(([value]) => 'sourceSeenAt' in value)).toBe(false) expect(mocks.onPage).toHaveBeenCalledWith([], expect.any(Date)) }) }) diff --git a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts index 6bf9faa6f3b..d4f0b5e964b 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts @@ -162,13 +162,13 @@ export async function runConnectorContentPass(input: ContentPassInput) { const prior = corpus.priorByExternalId.get(item.externalId) return prior && (!prior.contentHash || prior.contentHash !== item.contentHash) }) - /** A fresh grant must not publish an old body after the source document changed. */ + /** Revoke stale-body grants while retaining the content crawl's observation for EOF reconciliation. */ if (changed.length) await withLease(async (tx) => { for (let offset = 0; offset < changed.length; offset += 500) { await tx .update(document) - .set({ acl: [], aclRequirements: [], aclVerifiedAt: null, sourceSeenAt: null }) + .set({ acl: [], aclRequirements: [], aclVerifiedAt: null }) .where( and( eq(document.connectorId, input.connectorId), diff --git a/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts b/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts index 17863d0cb6c..9940e21f06a 100644 --- a/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts +++ b/apps/sim/lib/knowledge/documents/ocr-source-validation.test.ts @@ -23,6 +23,27 @@ describe('OCR source preflight', () => { assertOcrSourceSupported(Buffer.from(LFS_POINTER.replaceAll('\n', '\r\n')), 'image/png') ).toThrow(expect.objectContaining({ code: 'invalid_file' })) }) + it.each(['\n', '\r\n'])('recognizes extension records with %j line endings', (newline) => { + const pointer = LFS_POINTER.replace( + 'oid sha256:', + `ext-0-compress sha256:${'b'.repeat(64)}\next-1-encrypt sha256:${'c'.repeat(64)}\noid sha256:` + ).replaceAll('\n', newline) + expect(() => assertOcrSourceSupported(Buffer.from(pointer), 'image/png')).toThrow( + expect.objectContaining({ + code: 'invalid_file', + message: expect.stringContaining('Git LFS pointer'), + }) + ) + expect(() => + assertOcrSourceSupported(Buffer.from(`${pointer}arbitrary trailing content`), 'image/png') + ).not.toThrow() + expect(() => + assertOcrSourceSupported( + Buffer.from(pointer.replace('ext-0-compress sha256:', 'ext-0-compress sha1:')), + 'image/png' + ) + ).not.toThrow() + }) it('preserves small images and files containing a pointer as text', () => { const png = Buffer.from( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/l9sAAAAASUVORK5CYII=', diff --git a/apps/sim/lib/knowledge/documents/ocr-source-validation.ts b/apps/sim/lib/knowledge/documents/ocr-source-validation.ts index f6e8ebed8d9..e2308f7c1c3 100644 --- a/apps/sim/lib/knowledge/documents/ocr-source-validation.ts +++ b/apps/sim/lib/knowledge/documents/ocr-source-validation.ts @@ -3,7 +3,7 @@ import { PermanentDocumentProcessingError } from '@/lib/knowledge/documents/docu /** Canonical Git LFS pointers are text records smaller than 1 KiB, not the referenced binary. */ const GIT_LFS_POINTER_MAX_BYTES = 1024 const GIT_LFS_POINTER_PATTERN = - /^version https:\/\/git-lfs\.github\.com\/spec\/v1\r?\noid sha256:[a-f0-9]{64}\r?\nsize [0-9]+\r?\n$/ + /^version https:\/\/git-lfs\.github\.com\/spec\/v1\r?\n(?:ext-[0-9]+-[a-z0-9.-]+ sha256:[a-f0-9]{64}\r?\n)*oid sha256:[a-f0-9]{64}\r?\nsize [0-9]+\r?\n$/ function invalidSource(message: string): never { throw new PermanentDocumentProcessingError('invalid_file', message) diff --git a/apps/sim/lib/knowledge/documents/processing-recovery.ts b/apps/sim/lib/knowledge/documents/processing-recovery.ts index 35cee7413cb..ec44db492c9 100644 --- a/apps/sim/lib/knowledge/documents/processing-recovery.ts +++ b/apps/sim/lib/knowledge/documents/processing-recovery.ts @@ -42,18 +42,20 @@ async function recoverStoredDocuments( signal: AbortSignal ): Promise { let recovered = 0 - const attemptedKnowledgeBases = new Set() + const blockedKnowledgeBases = new Set() + const attemptedConnectors = new Set() for (let batch = 0; batch < MAX_RECOVERY_CANDIDATE_BATCHES; batch++) { if (recovered >= DOCUMENT_RECOVERY_BATCH_SIZE || Date.now() >= deadlineAt) break - const ownersBefore = attemptedKnowledgeBases.size + const connectorsBefore = attemptedConnectors.size recovered += await recoverStoredDocumentBatch( now, deadlineAt, signal, - attemptedKnowledgeBases, + blockedKnowledgeBases, + attemptedConnectors, DOCUMENT_RECOVERY_BATCH_SIZE - recovered ) - if (attemptedKnowledgeBases.size === ownersBefore) break + if (attemptedConnectors.size === connectorsBefore) break } return recovered } @@ -62,7 +64,8 @@ async function recoverStoredDocumentBatch( now: Date, deadlineAt: number, signal: AbortSignal, - attemptedKnowledgeBases: Set, + blockedKnowledgeBases: Set, + attemptedConnectors: Set, limit: number ): Promise { /** Discovery does not claim work. Ownership is rechecked under lifecycle locks below. */ @@ -76,7 +79,7 @@ async function recoverStoredDocumentBatch( .select({ id: document.id, knowledgeBaseId: document.knowledgeBaseId, - connectorId: document.connectorId, + connectorId: knowledgeConnector.id, workspaceId: knowledgeBase.workspaceId, organizationId: knowledgeBase.organizationId, }) @@ -86,8 +89,11 @@ async function recoverStoredDocumentBatch( .where( and( documentProcessingRecoveryCondition(now), - attemptedKnowledgeBases.size > 0 - ? notInArray(document.knowledgeBaseId, [...attemptedKnowledgeBases]) + blockedKnowledgeBases.size > 0 + ? notInArray(document.knowledgeBaseId, [...blockedKnowledgeBases]) + : undefined, + attemptedConnectors.size > 0 + ? notInArray(document.connectorId, [...attemptedConnectors]) : undefined, isNull(knowledgeBase.deletedAt), isNull(knowledgeConnector.deletedAt), @@ -110,8 +116,10 @@ async function recoverStoredDocumentBatch( } for (const [knowledgeBaseId, group] of groups) { if (Date.now() >= deadlineAt) break - attemptedKnowledgeBases.add(knowledgeBaseId) + const connectorIds = [...new Set(group.map((row) => row.connectorId))] + for (const connectorId of connectorIds) attemptedConnectors.add(connectorId) const owner = group[0] + let ownerVerified = false try { signal.throwIfAborted() const attribution = owner.organizationId @@ -139,16 +147,18 @@ async function recoverStoredDocumentBatch( .where(and(eq(knowledgeBase.id, knowledgeBaseId), isNull(knowledgeBase.deletedAt))) .for('share', { skipLocked: true }) signal.throwIfAborted() - if (!kb) return 0 + if (!kb) { + blockedKnowledgeBases.add(knowledgeBaseId) + return 0 + } assertBillingAttributionOwner(attribution, kb) + ownerVerified = true const connectors = await tx .select({ id: knowledgeConnector.id }) .from(knowledgeConnector) .where( and( - inArray(knowledgeConnector.id, [ - ...new Set(group.flatMap((row) => (row.connectorId ? [row.connectorId] : []))), - ]), + inArray(knowledgeConnector.id, connectorIds), eq(knowledgeConnector.knowledgeBaseId, knowledgeBaseId), inArray(knowledgeConnector.status, RECOVERABLE_CONNECTOR_STATUSES), isNull(knowledgeConnector.archivedAt), @@ -231,6 +241,7 @@ async function recoverStoredDocumentBatch( }) } catch (error) { signal.throwIfAborted() + if (!ownerVerified) blockedKnowledgeBases.add(knowledgeBaseId) logger.error('Stored document recovery admission failed', { knowledgeBaseId, diagnostic: getConnectorFailureDiagnostic(error), From e07224bbdab58d54ed151340e1e73ae7c3674a7c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 17 Sep 2026 12:22:56 -0700 Subject: [PATCH 3/4] improvement(search): generalize connector partition progress --- .github/workflows/test-build.yml | 2 +- .../connectors/google-drive/company-crawl.ts | 15 +- .../google-workspace/company-crawl.ts | 15 +- .../google-workspace/company-work.ts | 8 + .../connector-partition-work.integration.ts | 539 ++++++++++++++++++ .../google-company-work.integration.ts | 398 ------------- .../listing-continuation.integration.ts | 13 +- .../google-company-scheduler.test.ts | 117 ++-- .../connectors/google-company-scheduler.ts | 78 +-- .../connectors/listing-checkpoint.test.ts | 94 +++ ...le-company-store.ts => partition-store.ts} | 71 +-- .../knowledge/connectors/partition-work.ts | 54 ++ .../knowledge/connectors/sync-content-pass.ts | 17 +- .../0356_connector_partition_progress.sql | 28 + .../0356_google_company_user_progress.sql | 27 - .../db/migrations/meta/0356_snapshot.json | 444 ++++++++------- packages/db/migrations/meta/_journal.json | 4 +- packages/db/schema.ts | 31 +- packages/testing/src/mocks/schema.mock.ts | 35 +- 19 files changed, 1153 insertions(+), 837 deletions(-) create mode 100644 apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts delete mode 100644 apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts rename apps/sim/lib/knowledge/connectors/{google-company-store.ts => partition-store.ts} (76%) create mode 100644 apps/sim/lib/knowledge/connectors/partition-work.ts create mode 100644 packages/db/migrations/0356_connector_partition_progress.sql delete mode 100644 packages/db/migrations/0356_google_company_user_progress.sql diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 323941874ad..af187705e13 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -238,7 +238,7 @@ jobs: lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts lib/knowledge/__integration__/connector-deferral.integration.ts lib/knowledge/__integration__/stored-document-recovery.integration.ts - lib/knowledge/__integration__/google-company-work.integration.ts + lib/knowledge/__integration__/connector-partition-work.integration.ts lib/knowledge/__integration__/listing-continuation.integration.ts lib/knowledge/__integration__/kb-block-search.integration.ts lib/core/outbox/service.integration.ts diff --git a/apps/sim/connectors/google-drive/company-crawl.ts b/apps/sim/connectors/google-drive/company-crawl.ts index d56d9d7c6cb..ea9fb997863 100644 --- a/apps/sim/connectors/google-drive/company-crawl.ts +++ b/apps/sim/connectors/google-drive/company-crawl.ts @@ -6,7 +6,10 @@ import { GOOGLE_WORKSPACE_DRIVES_PAGE_SIZE, listGoogleWorkspaceDrives, } from '@/connectors/google-drive/workspace-drives' -import type { GoogleCompanyCursorAdapter } from '@/connectors/google-workspace/company-work' +import { + type GoogleCompanyCursorAdapter, + googleCompanyUserContextSchema, +} from '@/connectors/google-workspace/company-work' import { GOOGLE_WORKSPACE_USERS_PAGE_SIZE, getGoogleWorkspaceUser, @@ -19,15 +22,7 @@ import { parseOptionalUnlimitedSafeInteger } from '@/connectors/utils' const CURSOR_PREFIX = 'gdrive-company:v1:' const MAX_CURSOR_BYTES = 384 * 1024 const cursorSchema = z.object({ - users: z - .array( - z.object({ - id: z.string().min(1).max(256), - email: z.string().email().max(254), - customerId: z.string().min(1).max(256), - }) - ) - .max(GOOGLE_WORKSPACE_USERS_PAGE_SIZE), + users: z.array(googleCompanyUserContextSchema).max(GOOGLE_WORKSPACE_USERS_PAGE_SIZE), nextUsersPageToken: z.string().min(1).max(8192).optional(), scope: z.discriminatedUnion('kind', [ z.object({ diff --git a/apps/sim/connectors/google-workspace/company-crawl.ts b/apps/sim/connectors/google-workspace/company-crawl.ts index 86eecd79432..6202173cee9 100644 --- a/apps/sim/connectors/google-workspace/company-crawl.ts +++ b/apps/sim/connectors/google-workspace/company-crawl.ts @@ -3,7 +3,10 @@ import { normalizeEmail } from '@sim/utils/string' import { z } from 'zod' import { mapWithConcurrency } from '@/lib/core/utils/concurrency' import { GoogleApiError } from '@/connectors/google-workspace/api-errors' -import type { GoogleCompanyCursorAdapter } from '@/connectors/google-workspace/company-work' +import { + type GoogleCompanyCursorAdapter, + googleCompanyUserContextSchema, +} from '@/connectors/google-workspace/company-work' import { GOOGLE_WORKSPACE_USERS_PAGE_SIZE, type GoogleWorkspaceUser, @@ -29,15 +32,7 @@ const MAX_PROVIDER_CURSOR_BYTES = 256 * 1024 const MAX_PAGE_DOCUMENTS = 2500 const cursorSchema = z.object({ provider: z.enum(['gmail', 'google_calendar']), - users: z - .array( - z.object({ - id: z.string().min(1).max(256), - email: z.string().email().max(254), - customerId: z.string().min(1).max(256), - }) - ) - .max(GOOGLE_WORKSPACE_USERS_PAGE_SIZE), + users: z.array(googleCompanyUserContextSchema).max(GOOGLE_WORKSPACE_USERS_PAGE_SIZE), nextUsersPageToken: z.string().min(1).max(8192).optional(), providerCursor: z.string().min(1).max(MAX_PROVIDER_CURSOR_BYTES).optional(), listingFailures: listingFailuresSchema.optional(), diff --git a/apps/sim/connectors/google-workspace/company-work.ts b/apps/sim/connectors/google-workspace/company-work.ts index 012d45b09d1..75ca922326a 100644 --- a/apps/sim/connectors/google-workspace/company-work.ts +++ b/apps/sim/connectors/google-workspace/company-work.ts @@ -1,3 +1,4 @@ +import { z } from 'zod' import type { GoogleWorkspaceUser } from '@/connectors/google-workspace/users' /** Directory identity and provider continuation are persisted separately from delegated credentials. */ @@ -11,3 +12,10 @@ export interface GoogleCompanyCursorAdapter { /** Retains the exact active page when adopting the former single-user-at-a-time cursor. */ resume: (cursor: string) => GoogleCompanyUserWork[] } + +/** Validates Google Directory identity before persisted partition context is used for delegation. */ +export const googleCompanyUserContextSchema = z.object({ + id: z.string().min(1).max(256), + email: z.string().email().max(254), + customerId: z.string().min(1).max(256), +}) diff --git a/apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts new file mode 100644 index 00000000000..1b6d75b2985 --- /dev/null +++ b/apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts @@ -0,0 +1,539 @@ +/** Real PostgreSQL coverage for provider-independent partition work and atomic checkpoint advancement. */ +import { db } from '@sim/db' +import { + knowledgeConnector, + knowledgeConnectorPartition, + organization, + user, + workspace, +} from '@sim/db/schema' +import { and, eq, inArray, sql } from 'drizzle-orm' +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { z } from 'zod' +import { seedKnowledgeAclFixture } from '@/lib/knowledge/__integration__/seed-source-access-fixture' +import { beginListingCheckpoint } from '@/lib/knowledge/connectors/listing-checkpoint' +import { + commitConnectorPartitionWork, + connectorPartitionWorkStore, +} from '@/lib/knowledge/connectors/partition-store' +import type { ConnectorPartitionWorkUpdate } from '@/lib/knowledge/connectors/partition-work' +import { assertSyncLeaseHeldInTx, stillHoldsSyncLock } from '@/lib/knowledge/connectors/sync-lock' + +const spaceContextSchema = z + .object({ + spaceKey: z.string().min(1).max(128), + siteId: z.string().min(1).max(256), + }) + .strict() +const parseContext = (value: unknown) => spaceContextSchema.parse(value) + +describe('Connector partition checkpoint storage', () => { + let owner: Awaited> + const generationId = 'partition-fixture-generation' + const generationStartedAt = new Date(Date.now() - 13 * 60 * 60 * 1000) + const permissionRefreshAt = new Date(generationStartedAt.getTime() + 12 * 60 * 60_000) + const first = { + partitionKey: 'first', + context: { spaceKey: 'FIRST', siteId: 'fixture-site' }, + cursor: 'saved-provider-page-91', + } + const second = { + partitionKey: 'second', + context: { spaceKey: 'SECOND', siteId: 'fixture-site' }, + cursor: 'provider-first-page', + } + const checkpoint = beginListingCheckpoint({ + fingerprint: 'a'.repeat(64), + generationId, + startedAt: generationStartedAt, + }) + + beforeEach(async () => { + owner = await seedKnowledgeAclFixture(undefined, { connectorType: 'confluence' }) + await db.transaction(async (tx) => { + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { enqueue: [first, second] }, + permissionRefreshAt + ) + await tx + .update(knowledgeConnector) + .set({ listingCheckpoint: checkpoint }) + .where(eq(knowledgeConnector.id, owner.connectorId)) + }) + }) + afterEach(async () => { + vi.unstubAllEnvs() + await db.delete(workspace).where(eq(workspace.id, owner.workspaceId)) + await db.delete(organization).where(eq(organization.id, owner.organizationId)) + await db.delete(user).where(inArray(user.id, [owner.aliceId, owner.bobId])) + }) + afterAll(() => db.$client.end()) + + it('round-trips provider-owned space metadata without Google identity fields', async () => { + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + expect(await store.get('first', 'content')).toMatchObject({ + partitionKey: first.partitionKey, + context: first.context, + cursor: first.cursor, + }) + expect((await store.next('content', new Date()))?.context).toEqual(first.context) + const [row] = await db + .select({ permissionRetryAt: knowledgeConnectorPartition.permissionRetryAt }) + .from(knowledgeConnectorPartition) + .where( + and( + eq(knowledgeConnectorPartition.connectorId, owner.connectorId), + eq(knowledgeConnectorPartition.partitionKey, first.partitionKey) + ) + ) + expect(row.permissionRetryAt).toEqual(permissionRefreshAt) + }) + + it('rejects malformed stored provider metadata before exposing work to its caller', async () => { + await db + .update(knowledgeConnectorPartition) + .set({ context: { spaceKey: 7, siteId: 'fixture-site' } }) + .where(eq(knowledgeConnectorPartition.connectorId, owner.connectorId)) + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + await expect(store.get('first', 'content')).rejects.toMatchObject({ name: 'ZodError' }) + await expect(store.next('content', new Date())).rejects.toMatchObject({ name: 'ZodError' }) + }) + + it.each([ + { label: 'null', context: null }, + { label: 'array', context: [] }, + { label: 'string', context: 'not-an-object' }, + { label: 'number', context: 7 }, + ])('rejects non-object JSON metadata: $label', async ({ context }) => { + await expect( + db.execute(sql` + UPDATE ${knowledgeConnectorPartition} + SET context = ${JSON.stringify(context)}::jsonb + WHERE connector_id = ${owner.connectorId} + `) + ).rejects.toMatchObject({ cause: { code: '23514', constraint_name: 'kcp_context_check' } }) + }) + + it('requires context rather than silently inserting empty metadata', async () => { + await expect( + db.execute(sql` + INSERT INTO ${knowledgeConnectorPartition} + (connector_id, partition_key, generation_id, permission_retry_at) + VALUES (${owner.connectorId}, 'missing-context', ${generationId}, now()) + `) + ).rejects.toMatchObject({ cause: { code: '23502', column_name: 'context' } }) + }) + + it('accepts the context byte limit and rejects larger ASCII and multibyte objects', async () => { + await db.execute(sql` + UPDATE ${knowledgeConnectorPartition} + SET context = jsonb_build_object( + 'payload', repeat('a', 16384 - octet_length(jsonb_build_object('payload', '')::text)) + ) + WHERE connector_id = ${owner.connectorId} + `) + const [row] = await db + .select({ bytes: sql`octet_length(${knowledgeConnectorPartition.context}::text)` }) + .from(knowledgeConnectorPartition) + .where(eq(knowledgeConnectorPartition.connectorId, owner.connectorId)) + .limit(1) + expect(row.bytes).toBe(16384) + for (const payload of ['a'.repeat(16384), 'あ'.repeat(5500)]) { + await expect( + db + .update(knowledgeConnectorPartition) + .set({ context: { payload } }) + .where(eq(knowledgeConnectorPartition.connectorId, owner.connectorId)) + ).rejects.toMatchObject({ cause: { code: '23514', constraint_name: 'kcp_context_check' } }) + } + }) + + it.each([ + ['empty', ''], + ['oversized ASCII', 'a'.repeat(1025)], + ['oversized multibyte', 'あ'.repeat(342)], + ])('rejects %s partition keys', async (_label, partitionKey) => { + await expect( + db + .update(knowledgeConnectorPartition) + .set({ partitionKey }) + .where( + and( + eq(knowledgeConnectorPartition.connectorId, owner.connectorId), + eq(knowledgeConnectorPartition.partitionKey, first.partitionKey) + ) + ) + ).rejects.toMatchObject({ + cause: { code: '23514', constraint_name: 'kcp_partition_key_check' }, + }) + }) + + it('accepts a partition key at the byte limit', async () => { + const partitionKey = 'a'.repeat(1024) + await db + .update(knowledgeConnectorPartition) + .set({ partitionKey }) + .where( + and( + eq(knowledgeConnectorPartition.connectorId, owner.connectorId), + eq(knowledgeConnectorPartition.partitionKey, first.partitionKey) + ) + ) + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + expect((await store.get(partitionKey, 'content'))?.context).toEqual(first.context) + }) + + it('rolls back partition progress and the owning checkpoint together', async () => { + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + const before = await store.get('first', 'content') + await expect( + db.transaction(async (tx) => { + await assertSyncLeaseHeldInTx(tx, owner.connectorId, { + stillHeld: () => stillHoldsSyncLock(owner.connectorId, owner.lockId), + }) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'first', + kind: 'content', + cursor: 'next-page-92', + completed: false, + retryAt: new Date(), + attempts: 0, + failure: null, + }, + }, + permissionRefreshAt + ) + await tx + .update(knowledgeConnector) + .set({ listingCheckpoint: { ...checkpoint, cursor: 'outer-next-page' } }) + .where(eq(knowledgeConnector.id, owner.connectorId)) + throw new Error('simulated worker transaction failure') + }) + ).rejects.toThrow('simulated worker transaction failure') + expect(await store.get('first', 'content')).toEqual(before) + const [connector] = await db + .select({ checkpoint: knowledgeConnector.listingCheckpoint }) + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, owner.connectorId)) + expect(connector.checkpoint).toEqual(checkpoint) + }) + + it('pins provider replay, preserves it during repeated partition discovery and refreshes old evidence promptly', async () => { + await db.transaction(async (tx) => { + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { pin: { partitionKey: 'first', kind: 'content', cursor: 'stable-listing-snapshot' } }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { enqueue: [{ ...first, cursor: 'must-not-overwrite' }] }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'first', + kind: 'content', + cursor: 'next-content-page', + completed: false, + retryAt: new Date(), + attempts: 0, + failure: null, + }, + }, + permissionRefreshAt + ) + }) + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + expect((await store.get('first', 'content'))?.cursor).toBe('next-content-page') + expect((await store.next('content', new Date()))?.partitionKey).toBe('second') + expect((await store.next('permissions', new Date()))?.partitionKey).toBe('first') + expect(await store.get('first', 'permissions')).toMatchObject({ + cursor: undefined, + attempts: 0, + }) + }) + + it('retains blocked work independently of healthy completions and excludes another generation', async () => { + await db.transaction(async (tx) => { + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'first', + kind: 'content', + cursor: 'next-content-page', + completed: false, + retryAt: new Date(Date.now() + 60 * 60 * 1000), + attempts: 1, + failure: { + scope: first.context.spaceKey, + operation: 'confluence.pages.list', + status: 403, + reasons: [], + }, + }, + }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'second', + kind: 'content', + cursor: null, + completed: true, + retryAt: new Date(Date.now() + 60 * 60 * 1000), + attempts: 0, + failure: null, + }, + }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + 'different-generation', + { + enqueue: [ + { partitionKey: 'other', context: { spaceKey: 'OTHER', siteId: 'fixture-site' } }, + ], + }, + permissionRefreshAt + ) + }) + const store = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + expect(await store.next('content', new Date())).toBeNull() + expect(await store.remaining()).toMatchObject({ + count: 1, + failures: { count: 1, samples: [{ scope: first.context.spaceKey, status: 403 }] }, + }) + expect((await store.get('first', 'content'))?.cursor).toBe('next-content-page') + expect(await store.get('other', 'content')).toBeNull() + expect( + await connectorPartitionWorkStore('other-connector', generationId, parseContext).remaining() + ).toEqual({ + count: 0, + retryAt: null, + }) + }) + + it('does not rescan completed manual work or use its past retry time to wake blocked work', async () => { + await commitConnectorPartitionWork( + db, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'first', + kind: 'content', + cursor: 'blocked-partition-page', + completed: false, + retryAt: new Date(Date.now() + 60 * 60_000), + attempts: 1, + failure: { + scope: first.context.spaceKey, + operation: 'confluence.pages.list', + reasons: [], + }, + }, + }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + db, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'second', + kind: 'content', + cursor: null, + completed: true, + retryAt: new Date(Date.now() - 60_000), + attempts: 0, + failure: null, + }, + }, + permissionRefreshAt + ) + const automatic = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + const manual = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext, false) + expect((await automatic.next('content', new Date()))?.partitionKey).toBe('second') + expect(await manual.next('content', new Date())).toBeNull() + expect((await manual.remaining()).retryAt!.getTime()).toBeGreaterThan(Date.now()) + expect((await manual.next('permissions', new Date()))?.partitionKey).toBe('first') + await commitConnectorPartitionWork( + db, + owner.connectorId, + generationId, + { + update: { + partitionKey: 'first', + kind: 'content', + cursor: null, + completed: true, + retryAt: new Date(Date.now() - 60_000), + attempts: 0, + failure: null, + }, + }, + permissionRefreshAt + ) + expect(await automatic.next('content', new Date())).toBeNull() + expect((await automatic.remaining()).count).toBe(0) + }) + + it.each(['continuation', 'failure'] as const)( + 'retains a deferred permission %s after all content completes without keeping future refreshes open', + async (pending) => { + vi.stubEnv('TZ', 'Pacific/Honolulu') + const now = new Date() + const retryAt = new Date(now.getTime() + 30 * 60_000) + const nextPeriodicRefresh = new Date(now.getTime() + 12 * 60 * 60_000) + const update = ( + partitionKey: string, + kind: ConnectorPartitionWorkUpdate['kind'], + changes: Partial = {} + ) => + commitConnectorPartitionWork( + db, + owner.connectorId, + generationId, + { + update: { + partitionKey, + kind, + cursor: null, + completed: true, + retryAt: nextPeriodicRefresh, + attempts: 0, + failure: null, + permissionStartedAt: null, + ...changes, + }, + }, + permissionRefreshAt + ) + for (const partitionKey of ['first', 'second']) { + await update(partitionKey, 'permissions') + await update(partitionKey, 'content', { retryAt: new Date(now.getTime() - 60_000) }) + } + await update('first', 'permissions', { + cursor: pending === 'continuation' ? 'permission-page-9' : null, + completed: false, + retryAt, + attempts: pending === 'failure' ? 1 : 0, + failure: + pending === 'failure' + ? { + scope: first.context.spaceKey, + operation: 'confluence.pages.list', + status: 403, + reasons: [], + } + : null, + }) + const automatic = connectorPartitionWorkStore(owner.connectorId, generationId, parseContext) + const manual = connectorPartitionWorkStore( + owner.connectorId, + generationId, + parseContext, + false + ) + expect(await manual.remaining()).toMatchObject({ count: 1, retryAt }) + expect(await manual.next('content', now)).toBeNull() + expect(await automatic.next('content', now)).not.toBeNull() + expect(await automatic.next('permissions', now)).toBeNull() + expect((await automatic.next('permissions', retryAt))?.partitionKey).toBe('first') + for (const partitionKey of ['first', 'second']) { + await update(partitionKey, 'content', { + retryAt: new Date(now.getTime() + 2 * 60 * 60_000), + }) + } + expect(await automatic.remaining()).toMatchObject({ count: 1, retryAt }) + await update('first', 'permissions') + expect(await automatic.remaining()).toEqual({ count: 0, retryAt: null }) + expect(await manual.remaining()).toEqual({ count: 0, retryAt: null }) + expect(await automatic.next('content', new Date(now.getTime() + 3 * 60 * 60_000))).toBeNull() + } + ) + + it('resets both provider continuations while preserving due permissions, and cascades on connector deletion', async () => { + const permissionDueAt = new Date(Date.now() - 60_000) + await db + .update(knowledgeConnectorPartition) + .set({ permissionRetryAt: permissionDueAt }) + .where(eq(knowledgeConnectorPartition.connectorId, owner.connectorId)) + await db.transaction(async (tx) => { + await commitConnectorPartitionWork( + tx, + owner.connectorId, + generationId, + { + pin: { + partitionKey: 'first', + kind: 'permissions', + cursor: 'old-config-permission-page', + permissionStartedAt: generationStartedAt, + }, + }, + permissionRefreshAt + ) + await commitConnectorPartitionWork( + tx, + owner.connectorId, + 'new-config', + { enqueue: [{ ...first, cursor: 'new-config-first-page' }] }, + new Date(Date.now() + 12 * 60 * 60_000) + ) + }) + const store = connectorPartitionWorkStore(owner.connectorId, 'new-config', parseContext) + expect(await store.get('first', 'content')).toMatchObject({ + cursor: 'new-config-first-page', + attempts: 0, + }) + expect(await store.get('first', 'permissions')).toMatchObject({ + cursor: undefined, + permissionStartedAt: undefined, + }) + const [progress] = await db + .select({ permissionRetryAt: knowledgeConnectorPartition.permissionRetryAt }) + .from(knowledgeConnectorPartition) + .where( + and( + eq(knowledgeConnectorPartition.connectorId, owner.connectorId), + eq(knowledgeConnectorPartition.partitionKey, 'first') + ) + ) + expect(progress.permissionRetryAt).toEqual(permissionDueAt) + await db.delete(knowledgeConnector).where(eq(knowledgeConnector.id, owner.connectorId)) + expect( + await db + .select() + .from(knowledgeConnectorPartition) + .where(eq(knowledgeConnectorPartition.connectorId, owner.connectorId)) + ).toEqual([]) + }) +}) diff --git a/apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts b/apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts deleted file mode 100644 index 6906dcbef7e..00000000000 --- a/apps/sim/lib/knowledge/__integration__/google-company-work.integration.ts +++ /dev/null @@ -1,398 +0,0 @@ -/** Real PostgreSQL coverage for resumable Google user work and atomic checkpoint advancement. */ -import { db } from '@sim/db' -import { - knowledgeConnector, - knowledgeConnectorGoogleUser, - organization, - user, - workspace, -} from '@sim/db/schema' -import { and, eq, inArray } from 'drizzle-orm' -import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { seedKnowledgeAclFixture } from '@/lib/knowledge/__integration__/seed-source-access-fixture' -import type { GoogleCompanyWorkUpdate } from '@/lib/knowledge/connectors/google-company-scheduler' -import { - commitGoogleCompanyWork, - googleCompanyWorkStore, -} from '@/lib/knowledge/connectors/google-company-store' -import { beginListingCheckpoint } from '@/lib/knowledge/connectors/listing-checkpoint' -import { assertSyncLeaseHeldInTx, stillHoldsSyncLock } from '@/lib/knowledge/connectors/sync-lock' - -describe('Google company user checkpoint storage', () => { - let owner: Awaited> - const generationId = 'google-fixture-generation' - const generationStartedAt = new Date(Date.now() - 13 * 60 * 60 * 1000) - const first = { - user: { id: 'first', email: 'first@fixture.test', customerId: 'customer' }, - cursor: 'saved-provider-page-91', - } - const second = { - user: { id: 'second', email: 'second@fixture.test', customerId: 'customer' }, - cursor: 'provider-first-page', - } - const checkpoint = beginListingCheckpoint({ - fingerprint: 'a'.repeat(64), - generationId, - startedAt: generationStartedAt, - }) - - beforeEach(async () => { - owner = await seedKnowledgeAclFixture(undefined, { connectorType: 'google_drive' }) - await db.transaction(async (tx) => { - await commitGoogleCompanyWork( - tx, - owner.connectorId, - generationId, - { enqueue: [first, second] }, - generationStartedAt - ) - await tx - .update(knowledgeConnector) - .set({ listingCheckpoint: checkpoint }) - .where(eq(knowledgeConnector.id, owner.connectorId)) - }) - }) - afterEach(async () => { - vi.unstubAllEnvs() - await db.delete(workspace).where(eq(workspace.id, owner.workspaceId)) - await db.delete(organization).where(eq(organization.id, owner.organizationId)) - await db.delete(user).where(inArray(user.id, [owner.aliceId, owner.bobId])) - }) - afterAll(() => db.$client.end()) - - it('rolls back user progress and the owning checkpoint together', async () => { - const store = googleCompanyWorkStore(owner.connectorId, generationId) - const before = await store.get('first', 'content') - await expect( - db.transaction(async (tx) => { - await assertSyncLeaseHeldInTx(tx, owner.connectorId, { - stillHeld: () => stillHoldsSyncLock(owner.connectorId, owner.lockId), - }) - await commitGoogleCompanyWork( - tx, - owner.connectorId, - generationId, - { - update: { - userId: 'first', - kind: 'content', - cursor: 'next-page-92', - completed: false, - retryAt: new Date(), - attempts: 0, - failure: null, - }, - }, - generationStartedAt - ) - await tx - .update(knowledgeConnector) - .set({ listingCheckpoint: { ...checkpoint, cursor: 'outer-next-page' } }) - .where(eq(knowledgeConnector.id, owner.connectorId)) - throw new Error('simulated worker transaction failure') - }) - ).rejects.toThrow('simulated worker transaction failure') - expect(await store.get('first', 'content')).toEqual(before) - const [connector] = await db - .select({ checkpoint: knowledgeConnector.listingCheckpoint }) - .from(knowledgeConnector) - .where(eq(knowledgeConnector.id, owner.connectorId)) - expect(connector.checkpoint).toEqual(checkpoint) - }) - - it('pins provider replay, preserves it during repeated Directory discovery and refreshes old evidence promptly', async () => { - await db.transaction(async (tx) => { - await commitGoogleCompanyWork( - tx, - owner.connectorId, - generationId, - { pin: { userId: 'first', kind: 'content', cursor: 'stable-listing-snapshot' } }, - generationStartedAt - ) - await commitGoogleCompanyWork( - tx, - owner.connectorId, - generationId, - { enqueue: [{ ...first, cursor: 'must-not-overwrite' }] }, - generationStartedAt - ) - await commitGoogleCompanyWork( - tx, - owner.connectorId, - generationId, - { - update: { - userId: 'first', - kind: 'content', - cursor: 'next-content-page', - completed: false, - retryAt: new Date(), - attempts: 0, - failure: null, - }, - }, - generationStartedAt - ) - }) - const store = googleCompanyWorkStore(owner.connectorId, generationId) - expect((await store.get('first', 'content'))?.cursor).toBe('next-content-page') - expect((await store.next('content', new Date()))?.user.id).toBe('second') - expect((await store.next('permissions', new Date()))?.user.id).toBe('first') - expect(await store.get('first', 'permissions')).toMatchObject({ - cursor: undefined, - attempts: 0, - }) - }) - - it('retains blocked work independently of healthy completions and excludes another generation', async () => { - await db.transaction(async (tx) => { - await commitGoogleCompanyWork( - tx, - owner.connectorId, - generationId, - { - update: { - userId: 'first', - kind: 'content', - cursor: 'next-content-page', - completed: false, - retryAt: new Date(Date.now() + 60 * 60 * 1000), - attempts: 1, - failure: { - scope: first.user.email, - operation: 'drive.files.list', - status: 403, - reasons: [], - }, - }, - }, - generationStartedAt - ) - await commitGoogleCompanyWork( - tx, - owner.connectorId, - generationId, - { - update: { - userId: 'second', - kind: 'content', - cursor: null, - completed: true, - retryAt: new Date(Date.now() + 60 * 60 * 1000), - attempts: 0, - failure: null, - }, - }, - generationStartedAt - ) - await commitGoogleCompanyWork( - tx, - owner.connectorId, - 'different-generation', - { - enqueue: [{ user: { id: 'other', email: 'other@fixture.test', customerId: 'customer' } }], - }, - generationStartedAt - ) - }) - const store = googleCompanyWorkStore(owner.connectorId, generationId) - expect(await store.next('content', new Date())).toBeNull() - expect(await store.remaining()).toMatchObject({ - count: 1, - failures: { count: 1, samples: [{ scope: first.user.email, status: 403 }] }, - }) - expect((await store.get('first', 'content'))?.cursor).toBe('next-content-page') - expect(await store.get('other', 'content')).toBeNull() - expect(await googleCompanyWorkStore('other-connector', generationId).remaining()).toEqual({ - count: 0, - retryAt: null, - }) - }) - - it('does not rescan completed manual work or use its past retry time to wake blocked work', async () => { - await commitGoogleCompanyWork( - db, - owner.connectorId, - generationId, - { - update: { - userId: 'first', - kind: 'content', - cursor: 'blocked-user-page', - completed: false, - retryAt: new Date(Date.now() + 60 * 60_000), - attempts: 1, - failure: { scope: first.user.email, operation: 'drive.files.list', reasons: [] }, - }, - }, - generationStartedAt - ) - await commitGoogleCompanyWork( - db, - owner.connectorId, - generationId, - { - update: { - userId: 'second', - kind: 'content', - cursor: null, - completed: true, - retryAt: new Date(Date.now() - 60_000), - attempts: 0, - failure: null, - }, - }, - generationStartedAt - ) - const automatic = googleCompanyWorkStore(owner.connectorId, generationId) - const manual = googleCompanyWorkStore(owner.connectorId, generationId, false) - expect((await automatic.next('content', new Date()))?.user.id).toBe('second') - expect(await manual.next('content', new Date())).toBeNull() - expect((await manual.remaining()).retryAt!.getTime()).toBeGreaterThan(Date.now()) - expect((await manual.next('permissions', new Date()))?.user.id).toBe('first') - await commitGoogleCompanyWork( - db, - owner.connectorId, - generationId, - { - update: { - userId: 'first', - kind: 'content', - cursor: null, - completed: true, - retryAt: new Date(Date.now() - 60_000), - attempts: 0, - failure: null, - }, - }, - generationStartedAt - ) - expect(await automatic.next('content', new Date())).toBeNull() - expect((await automatic.remaining()).count).toBe(0) - }) - - it.each(['continuation', 'failure'] as const)( - 'retains a deferred permission %s after all content completes without keeping future refreshes open', - async (pending) => { - vi.stubEnv('TZ', 'Pacific/Honolulu') - const now = new Date() - const retryAt = new Date(now.getTime() + 30 * 60_000) - const nextPeriodicRefresh = new Date(now.getTime() + 12 * 60 * 60_000) - const update = ( - userId: string, - kind: GoogleCompanyWorkUpdate['kind'], - changes: Partial = {} - ) => - commitGoogleCompanyWork( - db, - owner.connectorId, - generationId, - { - update: { - userId, - kind, - cursor: null, - completed: true, - retryAt: nextPeriodicRefresh, - attempts: 0, - failure: null, - permissionStartedAt: null, - ...changes, - }, - }, - generationStartedAt - ) - for (const userId of ['first', 'second']) { - await update(userId, 'permissions') - await update(userId, 'content', { retryAt: new Date(now.getTime() - 60_000) }) - } - await update('first', 'permissions', { - cursor: pending === 'continuation' ? 'permission-page-9' : null, - completed: false, - retryAt, - attempts: pending === 'failure' ? 1 : 0, - failure: - pending === 'failure' - ? { - scope: first.user.email, - operation: 'drive.files.list', - status: 403, - reasons: [], - } - : null, - }) - const automatic = googleCompanyWorkStore(owner.connectorId, generationId) - const manual = googleCompanyWorkStore(owner.connectorId, generationId, false) - expect(await manual.remaining()).toMatchObject({ count: 1, retryAt }) - expect(await manual.next('content', now)).toBeNull() - expect(await automatic.next('content', now)).not.toBeNull() - expect(await automatic.next('permissions', now)).toBeNull() - expect((await automatic.next('permissions', retryAt))?.user.id).toBe('first') - for (const userId of ['first', 'second']) { - await update(userId, 'content', { retryAt: new Date(now.getTime() + 2 * 60 * 60_000) }) - } - expect(await automatic.remaining()).toMatchObject({ count: 1, retryAt }) - await update('first', 'permissions') - expect(await automatic.remaining()).toEqual({ count: 0, retryAt: null }) - expect(await manual.remaining()).toEqual({ count: 0, retryAt: null }) - expect(await automatic.next('content', new Date(now.getTime() + 3 * 60 * 60_000))).toBeNull() - } - ) - - it('resets both provider continuations while preserving due permissions, and cascades on connector deletion', async () => { - const permissionDueAt = new Date(Date.now() - 60_000) - await db - .update(knowledgeConnectorGoogleUser) - .set({ permissionRetryAt: permissionDueAt }) - .where(eq(knowledgeConnectorGoogleUser.connectorId, owner.connectorId)) - await db.transaction(async (tx) => { - await commitGoogleCompanyWork( - tx, - owner.connectorId, - generationId, - { - pin: { - userId: 'first', - kind: 'permissions', - cursor: 'old-config-permission-page', - permissionStartedAt: generationStartedAt, - }, - }, - generationStartedAt - ) - await commitGoogleCompanyWork( - tx, - owner.connectorId, - 'new-config', - { enqueue: [{ ...first, cursor: 'new-config-first-page' }] }, - new Date() - ) - }) - const store = googleCompanyWorkStore(owner.connectorId, 'new-config') - expect(await store.get('first', 'content')).toMatchObject({ - cursor: 'new-config-first-page', - attempts: 0, - }) - expect(await store.get('first', 'permissions')).toMatchObject({ - cursor: undefined, - permissionStartedAt: undefined, - }) - const [progress] = await db - .select({ permissionRetryAt: knowledgeConnectorGoogleUser.permissionRetryAt }) - .from(knowledgeConnectorGoogleUser) - .where( - and( - eq(knowledgeConnectorGoogleUser.connectorId, owner.connectorId), - eq(knowledgeConnectorGoogleUser.userId, 'first') - ) - ) - expect(progress.permissionRetryAt).toEqual(permissionDueAt) - await db.delete(knowledgeConnector).where(eq(knowledgeConnector.id, owner.connectorId)) - expect( - await db - .select() - .from(knowledgeConnectorGoogleUser) - .where(eq(knowledgeConnectorGoogleUser.connectorId, owner.connectorId)) - ).toEqual([]) - }) -}) diff --git a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts index 78433732ff9..8e6daf8dd7b 100644 --- a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts @@ -11,8 +11,8 @@ import { document, embedding, knowledgeConnector, - knowledgeConnectorGoogleUser, knowledgeConnectorMember, + knowledgeConnectorPartition, knowledgeConnectorSyncLog, knowledgeDocumentObservation, resourcePolicy, @@ -470,12 +470,11 @@ describe('durable source and member cycles in PostgreSQL', () => { processingStatus: 'completed', acl: [], }) - await db.insert(knowledgeConnectorGoogleUser).values({ + await db.insert(knowledgeConnectorPartition).values({ connectorId, generationId: checkpoint.generationId, - userId: 'google-user', - email: 'google-user@fixture.test', - customerId: 'customer', + partitionKey: 'google-user', + context: { id: 'google-user', email: 'google-user@fixture.test', customerId: 'customer' }, cursor: 'saved-content-page-91', lastServedAt: new Date(checkpoint.startedAt), permissionRetryAt: new Date(Date.now() + 12 * 60 * 60 * 1000), @@ -536,8 +535,8 @@ describe('durable source and member cycles in PostgreSQL', () => { expect(syncResult.docsUpdated).toBe(1) const [userProgress] = await db .select() - .from(knowledgeConnectorGoogleUser) - .where(eq(knowledgeConnectorGoogleUser.connectorId, connectorId)) + .from(knowledgeConnectorPartition) + .where(eq(knowledgeConnectorPartition.connectorId, connectorId)) expect(userProgress).toMatchObject({ generationId: checkpoint.generationId, cursor: null, diff --git a/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts b/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts index f637d713480..d066c6c422e 100644 --- a/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts +++ b/apps/sim/lib/knowledge/connectors/google-company-scheduler.test.ts @@ -1,19 +1,21 @@ /** @vitest-environment node */ + import { beforeEach, describe, expect, it, vi } from 'vitest' -import { - createGoogleCompanyScheduler, - type GoogleCompanyWorkChanges, - type GoogleCompanyWorkItem, - type GoogleCompanyWorkKind, - type GoogleCompanyWorkStore, -} from '@/lib/knowledge/connectors/google-company-scheduler' +import { createGoogleCompanyScheduler } from '@/lib/knowledge/connectors/google-company-scheduler' import { beginListingCheckpoint, type ListingCheckpoint, runResumableListing, } from '@/lib/knowledge/connectors/listing-checkpoint' +import type { + ConnectorPartitionWorkChanges, + ConnectorPartitionWorkItem, + ConnectorPartitionWorkKind, + ConnectorPartitionWorkStore, +} from '@/lib/knowledge/connectors/partition-work' import { GoogleDriveApiError } from '@/connectors/google-drive/google-drive-errors' import { GoogleApiError } from '@/connectors/google-workspace/api-errors' +import { googleCompanyUserContextSchema } from '@/connectors/google-workspace/company-work' import type { GoogleWorkspaceUser } from '@/connectors/google-workspace/users' import { ConnectorSourceError } from '@/connectors/source-error' import type { ConnectorConfig, ExternalDocument, ExternalListingFailures } from '@/connectors/types' @@ -34,7 +36,7 @@ const document: ExternalDocument = { function user(id: string): GoogleWorkspaceUser { return { id, email: `${id}@fixture.test`, customerId: 'customer', active: true } } -interface FakeWork extends GoogleCompanyWorkItem { +interface FakeWork extends ConnectorPartitionWorkItem { complete: boolean retryAt: Date served: number @@ -45,10 +47,10 @@ function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { let clock = new Date('2026-09-17T00:00:00Z') let served = 0 const rows = new Map() - const key = (id: string, kind: GoogleCompanyWorkKind) => `${id}:${kind}` + const key = (id: string, kind: ConnectorPartitionWorkKind) => `${id}:${kind}` const incomplete = (row: FakeWork) => row.kind === 'content' ? !row.complete : Boolean(row.cursor || row.failure) - const store: GoogleCompanyWorkStore = { + const store: ConnectorPartitionWorkStore = { get: async (id, kind) => rows.get(key(id, kind)) ?? null, next: async (kind, now) => [...rows.values()] @@ -59,9 +61,10 @@ function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { (kind !== 'content' || !row.complete || (syncIntervalMinutes > 0 && [...rows.values()].some(incomplete))) && - (kind === 'content' || (rows.get(key(row.user.id, 'content'))?.served ?? 0) > 0) + (kind === 'content' || (rows.get(key(row.partitionKey, 'content'))?.served ?? 0) > 0) ) - .sort((a, b) => a.served - b.served || a.user.id.localeCompare(b.user.id))[0] ?? null, + .sort((a, b) => a.served - b.served || a.partitionKey.localeCompare(b.partitionKey))[0] ?? + null, remaining: async () => { const remaining = [...rows.values()].filter(incomplete) const retryable = [...rows.values()].filter( @@ -69,7 +72,7 @@ function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { ) const failed = [...rows.values()].filter((row) => row.failure) return { - count: new Set(remaining.map((row) => row.user.id)).size, + count: new Set(remaining.map((row) => row.partitionKey)).size, retryAt: remaining.length ? (retryable.sort((a, b) => +a.retryAt - +b.retryAt)[0]?.retryAt ?? null) : null, @@ -84,11 +87,12 @@ function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { } }, } - const apply = (changes: GoogleCompanyWorkChanges) => { + const apply = (changes: ConnectorPartitionWorkChanges) => { for (const item of changes.enqueue ?? []) { - if (rows.has(key(item.user.id, 'content'))) continue - rows.set(key(item.user.id, 'content'), { + if (rows.has(key(item.partitionKey, 'content'))) continue + rows.set(key(item.partitionKey, 'content'), { ...item, + context: { ...googleCompanyUserContextSchema.parse(item.context), active: true }, kind: 'content', attempts: 0, hasFailure: false, @@ -96,8 +100,9 @@ function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { retryAt: clock, served: 0, }) - rows.set(key(item.user.id, 'permissions'), { - user: item.user, + rows.set(key(item.partitionKey, 'permissions'), { + partitionKey: item.partitionKey, + context: { ...googleCompanyUserContextSchema.parse(item.context), active: true }, kind: 'permissions', attempts: 0, hasFailure: false, @@ -107,13 +112,13 @@ function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { }) } if (changes.pin) - Object.assign(rows.get(key(changes.pin.userId, changes.pin.kind))!, { + Object.assign(rows.get(key(changes.pin.partitionKey, changes.pin.kind))!, { cursor: changes.pin.cursor, permissionStartedAt: changes.pin.permissionStartedAt, }) if (changes.update) { const change = changes.update - Object.assign(rows.get(key(change.userId, change.kind))!, { + Object.assign(rows.get(key(change.partitionKey, change.kind))!, { cursor: change.cursor ?? undefined, complete: change.kind === 'content' && change.completed, retryAt: change.retryAt, @@ -123,9 +128,9 @@ function fixture(provider = 'google_calendar', syncIntervalMinutes = 60) { permissionStartedAt: change.permissionStartedAt ?? undefined, }) for (const kind of ['content', 'permissions'] as const) { - rows.get(key(change.userId, kind))!.hasFailure = Boolean( - rows.get(key(change.userId, 'content'))?.failure || - rows.get(key(change.userId, 'permissions'))?.failure + rows.get(key(change.partitionKey, kind))!.hasFailure = Boolean( + rows.get(key(change.partitionKey, 'content'))?.failure || + rows.get(key(change.partitionKey, 'permissions'))?.failure ) } } @@ -271,20 +276,58 @@ describe('durable Google company user scheduling', () => { expect(f.saved().resumeAt).toBe('2026-09-17T00:05:00.000Z') }) - it('adopts a legacy cursor without resetting the active provider page', async () => { - mocks.directory.mockResolvedValue({ users: [user('a'), user('z')] }) - const f = fixture() - const legacy = `google-workspace:v1:${Buffer.from(JSON.stringify({ provider: 'google_calendar', users: [user('a')], providerCursor: 'existing-page-91', nextUsersPageToken: 'old-directory' })).toString('base64url')}` - f.setSaved({ ...f.saved(), cursor: legacy, listedCount: 10 }) - await f.step(3) - expect(f.list.mock.calls[0]?.[2]).toContain('google-workspace:v1:') - const resume = JSON.parse( - Buffer.from(f.list.mock.calls[0]![2]!.split(':v1:')[1], 'base64url').toString() - ) - expect(resume.providerCursor).toBe('existing-page-91') - expect(f.saved().listedCount).toBe(11) - expect(mocks.directory.mock.calls[0]?.[1]).toBeUndefined() - }) + it.each([ + { + provider: 'gmail', + prefix: 'google-workspace:v1:', + state: { provider: 'gmail', providerCursor: 'existing-page-91' }, + }, + { + provider: 'google_calendar', + prefix: 'google-workspace:v1:', + state: { provider: 'google_calendar', providerCursor: 'existing-page-91' }, + }, + { + provider: 'google_drive', + prefix: 'gdrive-company:v1:', + state: { scope: { kind: 'user', cursor: 'existing-page-91' } }, + }, + { + provider: 'google_drive', + prefix: 'gdrive-company:v1:', + state: { scope: { kind: 'drives', pageToken: 'existing-drives-page-91' } }, + }, + { + provider: 'google_drive', + prefix: 'gdrive-company:v1:', + state: { + scope: { + kind: 'drive', + driveIds: ['shared-drive'], + nextPageToken: 'next-drives-page', + cursor: 'existing-page-91', + }, + }, + }, + ])( + 'adopts $provider legacy state $state without resetting the active page', + async ({ provider, prefix, state }) => { + mocks.directory.mockResolvedValue({ users: [user('a'), user('z')] }) + const f = fixture(provider) + const legacy = `${prefix}${Buffer.from(JSON.stringify({ ...state, users: [user('a')], nextUsersPageToken: 'old-directory' })).toString('base64url')}` + const generationId = f.saved().generationId + f.setSaved({ ...f.saved(), cursor: legacy, listedCount: 10 }) + await f.step(3) + expect(f.list.mock.calls[0]?.[2]).toContain(prefix) + const resume = JSON.parse( + Buffer.from(f.list.mock.calls[0]![2]!.slice(prefix.length), 'base64url').toString() + ) + expect(resume).toMatchObject(state) + expect(resume.users).toEqual([{ id: 'a', email: 'a@fixture.test', customerId: 'customer' }]) + expect(f.saved()).toMatchObject({ generationId, listedCount: 11 }) + expect(mocks.directory.mock.calls[0]?.[1]).toBeUndefined() + } + ) it('pins the provider snapshot before processing and replays it after interruption', async () => { mocks.directory.mockResolvedValue({ users: [user('a')] }) diff --git a/apps/sim/lib/knowledge/connectors/google-company-scheduler.ts b/apps/sim/lib/knowledge/connectors/google-company-scheduler.ts index aeae5ba99c1..8acdd0b9cfe 100644 --- a/apps/sim/lib/knowledge/connectors/google-company-scheduler.ts +++ b/apps/sim/lib/knowledge/connectors/google-company-scheduler.ts @@ -1,4 +1,8 @@ import { z } from 'zod' +import type { + ConnectorPartitionWorkChanges, + ConnectorPartitionWorkStore, +} from '@/lib/knowledge/connectors/partition-work' import { googleDriveCompanyCursorAdapter } from '@/connectors/google-drive/company-crawl' import { GoogleDriveApiError } from '@/connectors/google-drive/google-drive-errors' import { GoogleApiError } from '@/connectors/google-workspace/api-errors' @@ -20,7 +24,7 @@ import type { } from '@/connectors/types' const CURSOR_PREFIX = 'google-company-work:v2:' -const PERMISSION_REFRESH_MS = 12 * 60 * 60 * 1000 +export const GOOGLE_COMPANY_PERMISSION_REFRESH_MS = 12 * 60 * 60 * 1000 const DIRECTORY_REFRESH_MS = 60 * 60 * 1000 const MAX_UNRESOLVED_FAILURES_PER_PASS = 3 const cursorSchema = z.object({ @@ -35,44 +39,8 @@ const cursorSchema = z.object({ .optional(), }) type CompanyWorkCursor = z.infer -export type GoogleCompanyWorkKind = 'content' | 'permissions' type ListingFailure = ExternalListingFailures['samples'][number] -export interface GoogleCompanyWorkItem extends GoogleCompanyUserWork { - kind: GoogleCompanyWorkKind - attempts: number - hasFailure: boolean - permissionStartedAt?: Date -} - -export interface GoogleCompanyWorkUpdate { - userId: string - kind: GoogleCompanyWorkKind - cursor: string | null - completed: boolean - retryAt: Date - attempts: number - failure: ListingFailure | null - permissionStartedAt?: Date | null -} - -/** All writes are supplied to the caller's lease-guarded checkpoint transaction. */ -export interface GoogleCompanyWorkChanges { - enqueue?: GoogleCompanyUserWork[] - pin?: { userId: string; kind: GoogleCompanyWorkKind; cursor: string; permissionStartedAt?: Date } - update?: GoogleCompanyWorkUpdate -} - -export interface GoogleCompanyWorkStore { - get: (userId: string, kind: GoogleCompanyWorkKind) => Promise - next: (kind: GoogleCompanyWorkKind, now: Date) => Promise - remaining: () => Promise<{ - count: number - retryAt: Date | null - failures?: ExternalListingFailures - }> -} - function adapterFor(provider: string): GoogleCompanyCursorAdapter | null { if (provider === 'google_drive') return googleDriveCompanyCursorAdapter if (provider === 'gmail' || provider === 'google_calendar') @@ -143,7 +111,7 @@ function deferredUserFailure( /** Durable fair scheduling adds no parallel provider or database work. */ export function createGoogleCompanyScheduler(input: { provider: string - store: GoogleCompanyWorkStore + store: ConnectorPartitionWorkStore listDocuments: ConnectorConfig['listDocuments'] isListingCursorInvalidError?: ConnectorConfig['isListingCursorInvalidError'] syncIntervalMinutes: number @@ -152,7 +120,7 @@ export function createGoogleCompanyScheduler(input: { const adapter = adapterFor(input.provider) if (!adapter) throw new Error('Unsupported Google company provider') const now = input.now ?? (() => new Date()) - const changes = new Map() + const changes = new Map() let unresolvedFailures = 0 let permissionStartedAt: Date | undefined const initial = (): CompanyWorkCursor => ({ @@ -161,7 +129,7 @@ export function createGoogleCompanyScheduler(input: { revision: 0, permissionTurn: false, }) - const nextCursor = (state: CompanyWorkCursor, change: GoogleCompanyWorkChanges): string => { + const nextCursor = (state: CompanyWorkCursor, change: ConnectorPartitionWorkChanges): string => { const cursor = writeCursor({ ...state, active: undefined, revision: state.revision + 1 }) changes.set(cursor, change) return cursor @@ -179,7 +147,11 @@ export function createGoogleCompanyScheduler(input: { return { documents: [], hasMore: true, - nextCursor: nextCursor(initial(), { enqueue: adapter.resume(cursor) }), + nextCursor: nextCursor(initial(), { + enqueue: adapter + .resume(cursor) + .map(({ user, cursor }) => ({ partitionKey: user.id, context: user, cursor })), + }), } } let state = cursor ? readCursor(cursor) : initial() @@ -231,7 +203,7 @@ export function createGoogleCompanyScheduler(input: { { enqueue: users.map(({ id, email, customerId }) => { const user = { id, email, customerId } - return { user, cursor: adapter.seed(user) } + return { partitionKey: user.id, context: user, cursor: adapter.seed(user) } }), } ), @@ -274,7 +246,7 @@ export function createGoogleCompanyScheduler(input: { const active = { ...state, revision: state.revision + 1, - active: { userId: work.user.id, kind: work.kind }, + active: { userId: work.partitionKey, kind: work.kind }, } const currentCursor = writeCursor(active) const next = { @@ -304,9 +276,9 @@ export function createGoogleCompanyScheduler(input: { hasMore: true, nextCursor: nextCursor(next, { update: { - userId: work.user.id, + partitionKey: work.partitionKey, kind: work.kind, - cursor: resetCursor ? adapter.seed(work.user) : (work.cursor ?? null), + cursor: resetCursor ? adapter.seed(work.context) : (work.cursor ?? null), completed: false, retryAt, attempts: work.attempts + 1, @@ -329,25 +301,25 @@ export function createGoogleCompanyScheduler(input: { page = await input.listDocuments( accessToken, sourceConfig, - work.cursor ?? adapter.seed(work.user), + work.cursor ?? adapter.seed(work.context), syncContext ) } catch (error) { signal?.throwIfAborted() if (input.isListingCursorInvalidError?.(error)) return failed( - { scope: work.user.email, operation: 'google.user.cursor', reasons: [] }, + { scope: work.context.email, operation: 'google.user.cursor', reasons: [] }, false, true ) - const failure = deferredUserFailure(error, work.user) + const failure = deferredUserFailure(error, work.context) if (!failure) throw error return failed(failure, true) } if (page.listingFailures) return failed( page.listingFailures.samples[0] ?? { - scope: work.user.email, + scope: work.context.email, operation: 'google.user.list', reasons: [], } @@ -364,7 +336,7 @@ export function createGoogleCompanyScheduler(input: { if (page.currentCursor) changes.set(currentCursor, { pin: { - userId: work.user.id, + partitionKey: work.partitionKey, kind: work.kind, cursor: page.currentCursor, permissionStartedAt, @@ -377,7 +349,7 @@ export function createGoogleCompanyScheduler(input: { ...(work.kind === 'permissions' ? { permissionsOnly: true } : {}), nextCursor: nextCursor(next, { update: { - userId: work.user.id, + partitionKey: work.partitionKey, kind: work.kind, cursor: page.nextCursor ?? null, completed: !page.hasMore, @@ -387,7 +359,7 @@ export function createGoogleCompanyScheduler(input: { ? new Date( now().getTime() + (work.kind === 'permissions' - ? PERMISSION_REFRESH_MS + ? GOOGLE_COMPANY_PERMISSION_REFRESH_MS : input.syncIntervalMinutes * 60_000) ) : now(), @@ -401,7 +373,7 @@ export function createGoogleCompanyScheduler(input: { return { listDocuments, permissionStartedAt: () => permissionStartedAt, - changesFor: (cursor: string | null): GoogleCompanyWorkChanges | undefined => + changesFor: (cursor: string | null): ConnectorPartitionWorkChanges | undefined => cursor ? changes.get(cursor) : undefined, didCommit: (cursor: string | null) => { if (cursor) changes.delete(cursor) diff --git a/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts b/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts index fede008e1a3..5ef9f2ec0dc 100644 --- a/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts +++ b/apps/sim/lib/knowledge/connectors/listing-checkpoint.test.ts @@ -9,6 +9,14 @@ import { readListingCheckpoint, runResumableListing, } from '@/lib/knowledge/connectors/listing-checkpoint' +import { + googleDriveCompanyCursorAdapter, + InvalidGoogleCompanyCursor, +} from '@/connectors/google-drive/company-crawl' +import { + googleWorkspaceCompanyCursorAdapter, + InvalidGoogleWorkspaceCursor, +} from '@/connectors/google-workspace/company-crawl' import type { ExternalDocument } from '@/connectors/types' const fingerprint = listingFingerprint({ source: 'drive', folder: 'folder-1' }) @@ -234,6 +242,92 @@ describe('durable connector listing checkpoints', () => { expect(f.processPage.mock.calls[0][1].generationId).toBe(result.generationId) }) + it.each(['google_drive', 'gmail', 'google_calendar'] as const)( + 'safely restarts when the legacy %s reader receives a partition checkpoint', + async (provider) => { + const adapter = + provider === 'google_drive' + ? googleDriveCompanyCursorAdapter + : googleWorkspaceCompanyCursorAdapter(provider) + const InvalidCursorError = + provider === 'google_drive' ? InvalidGoogleCompanyCursor : InvalidGoogleWorkspaceCursor + const cursor = `google-company-work:v2:${Buffer.from( + JSON.stringify({ + directoryComplete: true, + directoryRefreshAt: '2026-09-08T11:00:00.000Z', + phase: 'users', + revision: 42, + permissionTurn: true, + active: { userId: 'user-1', kind: 'permissions' }, + }) + ).toString('base64url')}` + const f = fixture({ + ...checkpoint(), + cursor, + listedCount: 700, + unsafe: true, + contentFailures: true, + permissionFailures: true, + listingFailures: { + count: 1, + samples: [{ scope: 'user@example.com', operation: 'google.user.list', reasons: [] }], + }, + }) + const databaseTime = new Date('2026-09-08T10:00:00.000Z') + const getGenerationStartedAt = vi.fn(async () => databaseTime) + f.listDocuments.mockImplementation(async (_token, _source, savedCursor) => { + expect(savedCursor).toBe(cursor) + expect(() => adapter.resume(savedCursor)).toThrow(InvalidCursorError) + adapter.resume(savedCursor) + return { documents: [doc], hasMore: false } + }) + /** The provider readers and restart contract are shared with staging; its envelope parser is not emulated. */ + const restarted = await runResumableListing({ + ...f.input, + maxPages: 1, + getGenerationStartedAt, + connectorConfig: { + listDocuments: f.listDocuments, + isListingCursorInvalidError: (error) => error instanceof InvalidCursorError, + }, + }) + expect(f.processPage).not.toHaveBeenCalled() + expect(f.saveCheckpoint).toHaveBeenCalledOnce() + expect(getGenerationStartedAt).toHaveBeenCalledOnce() + expect(restarted.generationId).not.toBe('cycle-1') + expect(restarted).toMatchObject({ + fingerprint, + startedAt: databaseTime.toISOString(), + cursor: null, + complete: false, + listedCount: 0, + unsafe: false, + contentFailures: false, + permissionFailures: false, + listingFailures: null, + }) + expect(f.saved()).toEqual(restarted) + + const resumed = fixture(f.saved()) + resumed.listDocuments.mockResolvedValue({ + documents: [doc], + hasMore: true, + nextCursor: 'restarted-provider-page-2', + }) + const incomplete = await runResumableListing({ ...resumed.input, maxPages: 1 }) + expect(resumed.listDocuments.mock.calls[0][2]).toBeUndefined() + expect(resumed.processPage).toHaveBeenCalledOnce() + expect(resumed.processPage.mock.calls[0][1].generationId).toBe(restarted.generationId) + expect(incomplete).toMatchObject({ + generationId: restarted.generationId, + startedAt: databaseTime.toISOString(), + cursor: 'restarted-provider-page-2', + complete: false, + listedCount: 1, + }) + } + ) + it('finishes a source larger than 50,000 documents without retaining its whole listing', async () => { const f = fixture() let processed = 0 diff --git a/apps/sim/lib/knowledge/connectors/google-company-store.ts b/apps/sim/lib/knowledge/connectors/partition-store.ts similarity index 76% rename from apps/sim/lib/knowledge/connectors/google-company-store.ts rename to apps/sim/lib/knowledge/connectors/partition-store.ts index 4ec644e5ead..02009652d1c 100644 --- a/apps/sim/lib/knowledge/connectors/google-company-store.ts +++ b/apps/sim/lib/knowledge/connectors/partition-store.ts @@ -1,22 +1,26 @@ import { db } from '@sim/db' -import { knowledgeConnectorGoogleUser } from '@sim/db/schema' +import { knowledgeConnectorPartition } from '@sim/db/schema' import { and, asc, eq, exists, isNotNull, lte, ne, or, sql } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' import type { - GoogleCompanyWorkChanges, - GoogleCompanyWorkItem, - GoogleCompanyWorkKind, - GoogleCompanyWorkStore, -} from '@/lib/knowledge/connectors/google-company-scheduler' + ConnectorPartitionWorkChanges, + ConnectorPartitionWorkItem, + ConnectorPartitionWorkKind, + ConnectorPartitionWorkStore, +} from '@/lib/knowledge/connectors/partition-work' import { listingFailuresSchema, MAX_LISTING_FAILURE_SAMPLES } from '@/connectors/listing-failures' -const PERMISSION_REFRESH_MS = 12 * 60 * 60 * 1000 -type Row = typeof knowledgeConnectorGoogleUser.$inferSelect -const work = knowledgeConnectorGoogleUser +type Row = typeof knowledgeConnectorPartition.$inferSelect +const work = knowledgeConnectorPartition -function project(row: Row, kind: GoogleCompanyWorkKind): GoogleCompanyWorkItem { +function project( + row: Row, + kind: ConnectorPartitionWorkKind, + parseContext: (value: unknown) => Context +): ConnectorPartitionWorkItem { return { - user: { id: row.userId, email: row.email, customerId: row.customerId }, + partitionKey: row.partitionKey, + context: parseContext(row.context), kind, cursor: (kind === 'content' ? row.cursor : row.permissionCursor) ?? undefined, attempts: kind === 'content' ? row.attempts : row.permissionAttempts, @@ -26,11 +30,12 @@ function project(row: Row, kind: GoogleCompanyWorkKind): GoogleCompanyWorkItem { } /** The connector's existing lease is the sole writer lease; no extra worker concurrency is introduced. */ -export function googleCompanyWorkStore( +export function connectorPartitionWorkStore( connectorId: string, generationId: string, + parseContext: (value: unknown) => Context, rescanCompleted = true -): GoogleCompanyWorkStore { +): ConnectorPartitionWorkStore { const scope = and(eq(work.connectorId, connectorId), eq(work.generationId, generationId)) const contentIncomplete = ne(work.status, 'complete') const permissionsIncomplete = or( @@ -39,20 +44,20 @@ export function googleCompanyWorkStore( ) const incomplete = or(contentIncomplete, permissionsIncomplete) return { - async get(userId, kind) { + async get(partitionKey, kind) { const [row] = await db .select() .from(work) - .where(and(scope, eq(work.userId, userId))) + .where(and(scope, eq(work.partitionKey, partitionKey))) .limit(1) - return row ? project(row, kind) : null + return row ? project(row, kind, parseContext) : null }, async next(kind, now) { - /** A completed sweep must reach EOF instead of starting another overdue user sweep. */ + /** A completed sweep must reach EOF instead of starting another overdue partition sweep. */ const contentEligible = rescanCompleted ? or( contentIncomplete, - exists(db.select({ id: work.userId }).from(work).where(and(scope, incomplete))) + exists(db.select({ id: work.partitionKey }).from(work).where(and(scope, incomplete))) ) : contentIncomplete const [row] = await db @@ -68,10 +73,10 @@ export function googleCompanyWorkStore( ) .orderBy( sql`${kind === 'content' ? work.lastServedAt : work.permissionLastServedAt} ASC NULLS FIRST`, - asc(work.userId) + asc(work.partitionKey) ) .limit(1) - return row ? project(row, kind) : null + return row ? project(row, kind, parseContext) : null }, async remaining() { const [counts] = await db @@ -90,7 +95,7 @@ export function googleCompanyWorkStore( .select({ failure: work.failure, permissionFailure: work.permissionFailure }) .from(work) .where(and(scope, or(isNotNull(work.failure), isNotNull(work.permissionFailure)))) - .orderBy(asc(work.userId)) + .orderBy(asc(work.partitionKey)) .limit(MAX_LISTING_FAILURE_SAMPLES) : [] return { @@ -110,12 +115,12 @@ export function googleCompanyWorkStore( } /** Called in the same transaction as the listing checkpoint, after that page's durable work. */ -export async function commitGoogleCompanyWork( +export async function commitConnectorPartitionWork( tx: DbOrTx, connectorId: string, generationId: string, - changes: GoogleCompanyWorkChanges, - generationStartedAt: Date + changes: ConnectorPartitionWorkChanges, + permissionRefreshAt: Date ): Promise { const scope = and(eq(work.connectorId, connectorId), eq(work.generationId, generationId)) if (changes.enqueue?.length) { @@ -123,23 +128,21 @@ export async function commitGoogleCompanyWork( await tx .insert(work) .values( - changes.enqueue.map(({ user, cursor }) => ({ + changes.enqueue.map(({ partitionKey, context, cursor }) => ({ connectorId, generationId, - userId: user.id, - email: user.email, - customerId: user.customerId, + partitionKey, + context, cursor: cursor ?? null, - permissionRetryAt: new Date(generationStartedAt.getTime() + PERMISSION_REFRESH_MS), + permissionRetryAt: permissionRefreshAt, })) ) /** Content restarts must not postpone the earlier deadline for refreshing stored permissions. */ .onConflictDoUpdate({ - target: [work.connectorId, work.userId], + target: [work.connectorId, work.partitionKey], set: { generationId, - email: sql`excluded.email`, - customerId: sql`excluded.customer_id`, + context: sql`excluded.context`, cursor: sql`excluded.cursor`, status: 'pending', attempts: 0, @@ -163,7 +166,7 @@ export async function commitGoogleCompanyWork( ? { cursor: pin.cursor } : { permissionCursor: pin.cursor, permissionStartedAt: pin.permissionStartedAt } ) - .where(and(scope, eq(work.userId, pin.userId))) + .where(and(scope, eq(work.partitionKey, pin.partitionKey))) } if (changes.update) { const update = changes.update @@ -189,6 +192,6 @@ export async function commitGoogleCompanyWork( permissionStartedAt: update.permissionStartedAt, } ) - .where(and(scope, eq(work.userId, update.userId))) + .where(and(scope, eq(work.partitionKey, update.partitionKey))) } } diff --git a/apps/sim/lib/knowledge/connectors/partition-work.ts b/apps/sim/lib/knowledge/connectors/partition-work.ts new file mode 100644 index 00000000000..a0bb80fb711 --- /dev/null +++ b/apps/sim/lib/knowledge/connectors/partition-work.ts @@ -0,0 +1,54 @@ +import type { ExternalListingFailures } from '@/connectors/types' + +export type ConnectorPartitionWorkKind = 'content' | 'permissions' +type ListingFailure = ExternalListingFailures['samples'][number] + +/** Provider adapters validate their own context; persistence owns only partition progress. */ +export interface ConnectorPartitionWorkItem { + partitionKey: string + context: Context + kind: ConnectorPartitionWorkKind + cursor?: string + attempts: number + hasFailure: boolean + permissionStartedAt?: Date +} + +export interface ConnectorPartitionWorkUpdate { + partitionKey: string + kind: ConnectorPartitionWorkKind + cursor: string | null + completed: boolean + retryAt: Date + attempts: number + failure: ListingFailure | null + permissionStartedAt?: Date | null +} + +/** All writes are supplied to the caller's lease-guarded checkpoint transaction. */ +export interface ConnectorPartitionWorkChanges { + enqueue?: { partitionKey: string; context: Record; cursor?: string }[] + pin?: { + partitionKey: string + kind: ConnectorPartitionWorkKind + cursor: string + permissionStartedAt?: Date + } + update?: ConnectorPartitionWorkUpdate +} + +export interface ConnectorPartitionWorkStore { + get: ( + partitionKey: string, + kind: ConnectorPartitionWorkKind + ) => Promise | null> + next: ( + kind: ConnectorPartitionWorkKind, + now: Date + ) => Promise | null> + remaining: () => Promise<{ + count: number + retryAt: Date | null + failures?: ExternalListingFailures + }> +} diff --git a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts index d4f0b5e964b..c11fb36b017 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts @@ -9,18 +9,19 @@ import { } from '@/lib/knowledge/connectors/access-modes' import { createGoogleCompanyScheduler, + GOOGLE_COMPANY_PERMISSION_REFRESH_MS, isGoogleCompanySource, } from '@/lib/knowledge/connectors/google-company-scheduler' -import { - commitGoogleCompanyWork, - googleCompanyWorkStore, -} from '@/lib/knowledge/connectors/google-company-store' import { beginListingCheckpoint, type ListingCheckpoint, readListingCheckpoint, runResumableListing, } from '@/lib/knowledge/connectors/listing-checkpoint' +import { + commitConnectorPartitionWork, + connectorPartitionWorkStore, +} from '@/lib/knowledge/connectors/partition-store' import { SOURCE_CONTENT_ERROR, SOURCE_PERMISSION_ERROR, @@ -45,6 +46,7 @@ import { } from '@/lib/knowledge/connectors/sync-primitives' import { hardDeleteDocuments } from '@/lib/knowledge/documents/service' import { SIM_SEARCH_SYNC_INTERVAL_MINUTES } from '@/lib/sim-search/constants' +import { googleCompanyUserContextSchema } from '@/connectors/google-workspace/company-work' import type { ConnectorConfig, ExternalDocument, SyncResult } from '@/connectors/types' interface ContentPassInput { @@ -120,9 +122,10 @@ export async function runConnectorContentPass(input: ContentPassInput) { input.connector.syncIntervalMinutes ?? SIM_SEARCH_SYNC_INTERVAL_MINUTES ) const companyStore = () => - googleCompanyWorkStore( + connectorPartitionWorkStore( input.connectorId, String(input.syncContext.syncRunId), + googleCompanyUserContextSchema.parse, syncIntervalMinutes > 0 ) const company = isGoogleCompanySource(input.connector.connectorType, input.syncContext) @@ -293,12 +296,12 @@ export async function runConnectorContentPass(input: ContentPassInput) { await withLease(async (tx) => { const changes = company?.changesFor(next.cursor) if (changes) - await commitGoogleCompanyWork( + await commitConnectorPartitionWork( tx, input.connectorId, next.generationId, changes, - new Date(next.startedAt) + new Date(new Date(next.startedAt).getTime() + GOOGLE_COMPANY_PERMISSION_REFRESH_MS) ) await tx .update(knowledgeConnector) diff --git a/packages/db/migrations/0356_connector_partition_progress.sql b/packages/db/migrations/0356_connector_partition_progress.sql new file mode 100644 index 00000000000..1f0bbb2cdc9 --- /dev/null +++ b/packages/db/migrations/0356_connector_partition_progress.sql @@ -0,0 +1,28 @@ +CREATE TABLE "knowledge_connector_partition" ( + "connector_id" text NOT NULL, + "partition_key" text NOT NULL, + "generation_id" text NOT NULL, + "context" jsonb NOT NULL, + "cursor" text, + "status" text DEFAULT 'pending' NOT NULL, + "attempts" integer DEFAULT 0 NOT NULL, + "retry_at" timestamp DEFAULT now() NOT NULL, + "last_served_at" timestamp, + "failure" jsonb, + "permission_cursor" text, + "permission_attempts" integer DEFAULT 0 NOT NULL, + "permission_retry_at" timestamp NOT NULL, + "permission_last_served_at" timestamp, + "permission_started_at" timestamp, + "permission_failure" jsonb, + CONSTRAINT "kcp_pk" PRIMARY KEY("connector_id","partition_key"), + CONSTRAINT "kcp_partition_key_check" CHECK (octet_length("knowledge_connector_partition"."partition_key") BETWEEN 1 AND 1024), + CONSTRAINT "kcp_context_check" CHECK (jsonb_typeof("knowledge_connector_partition"."context") = 'object' AND octet_length("knowledge_connector_partition"."context"::text) <= 16384), + CONSTRAINT "kcp_status_check" CHECK ("knowledge_connector_partition"."status" IN ('pending', 'complete', 'blocked')), + CONSTRAINT "kcp_cursor_check" CHECK (("knowledge_connector_partition"."cursor" IS NULL OR octet_length("knowledge_connector_partition"."cursor") <= 393216) AND ("knowledge_connector_partition"."permission_cursor" IS NULL OR octet_length("knowledge_connector_partition"."permission_cursor") <= 393216)), + CONSTRAINT "kcp_attempts_check" CHECK ("knowledge_connector_partition"."attempts" >= 0 AND "knowledge_connector_partition"."permission_attempts" >= 0) +); +--> statement-breakpoint +ALTER TABLE "knowledge_connector_partition" ADD CONSTRAINT "knowledge_connector_partition_connector_id_knowledge_connector_id_fk" FOREIGN KEY ("connector_id") REFERENCES "public"."knowledge_connector"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "kcp_content_due_idx" ON "knowledge_connector_partition" USING btree ("connector_id","generation_id","status","retry_at","last_served_at");--> statement-breakpoint +CREATE INDEX "kcp_permission_due_idx" ON "knowledge_connector_partition" USING btree ("connector_id","generation_id","permission_retry_at","permission_last_served_at"); \ No newline at end of file diff --git a/packages/db/migrations/0356_google_company_user_progress.sql b/packages/db/migrations/0356_google_company_user_progress.sql deleted file mode 100644 index 3bb4608f0dc..00000000000 --- a/packages/db/migrations/0356_google_company_user_progress.sql +++ /dev/null @@ -1,27 +0,0 @@ -CREATE TABLE "knowledge_connector_google_user" ( - "connector_id" text NOT NULL, - "user_id" text NOT NULL, - "generation_id" text NOT NULL, - "email" text NOT NULL, - "customer_id" text NOT NULL, - "cursor" text, - "status" text DEFAULT 'pending' NOT NULL, - "attempts" integer DEFAULT 0 NOT NULL, - "retry_at" timestamp DEFAULT now() NOT NULL, - "last_served_at" timestamp, - "failure" jsonb, - "permission_cursor" text, - "permission_attempts" integer DEFAULT 0 NOT NULL, - "permission_retry_at" timestamp NOT NULL, - "permission_last_served_at" timestamp, - "permission_started_at" timestamp, - "permission_failure" jsonb, - CONSTRAINT "kcgu_pk" PRIMARY KEY("connector_id","user_id"), - CONSTRAINT "kcgu_status_check" CHECK ("knowledge_connector_google_user"."status" IN ('pending', 'complete', 'blocked')), - CONSTRAINT "kcgu_cursor_check" CHECK (("knowledge_connector_google_user"."cursor" IS NULL OR octet_length("knowledge_connector_google_user"."cursor") <= 393216) AND ("knowledge_connector_google_user"."permission_cursor" IS NULL OR octet_length("knowledge_connector_google_user"."permission_cursor") <= 393216)), - CONSTRAINT "kcgu_attempts_check" CHECK ("knowledge_connector_google_user"."attempts" >= 0 AND "knowledge_connector_google_user"."permission_attempts" >= 0) -); ---> statement-breakpoint -ALTER TABLE "knowledge_connector_google_user" ADD CONSTRAINT "knowledge_connector_google_user_connector_id_knowledge_connector_id_fk" FOREIGN KEY ("connector_id") REFERENCES "public"."knowledge_connector"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "kcgu_content_due_idx" ON "knowledge_connector_google_user" USING btree ("connector_id","generation_id","status","retry_at","last_served_at");--> statement-breakpoint -CREATE INDEX "kcgu_permission_due_idx" ON "knowledge_connector_google_user" USING btree ("connector_id","generation_id","permission_retry_at","permission_last_served_at"); \ No newline at end of file diff --git a/packages/db/migrations/meta/0356_snapshot.json b/packages/db/migrations/meta/0356_snapshot.json index 2d5a68f21f9..2ef177f6e52 100644 --- a/packages/db/migrations/meta/0356_snapshot.json +++ b/packages/db/migrations/meta/0356_snapshot.json @@ -1,5 +1,5 @@ { - "id": "6cbb7342-0685-49b4-b9ef-637e9a092ac3", + "id": "91de1d0e-ad87-4ad5-bef8-2c77aa34f2f7", "prevId": "ae380daf-a120-4f9d-8908-caa1c0980f76", "version": "7", "dialect": "postgresql", @@ -9609,226 +9609,6 @@ }, "isRLSEnabled": false }, - "public.knowledge_connector_google_user": { - "name": "knowledge_connector_google_user", - "schema": "", - "columns": { - "connector_id": { - "name": "connector_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "generation_id": { - "name": "generation_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "customer_id": { - "name": "customer_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "cursor": { - "name": "cursor", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "retry_at": { - "name": "retry_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "last_served_at": { - "name": "last_served_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "failure": { - "name": "failure", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "permission_cursor": { - "name": "permission_cursor", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "permission_attempts": { - "name": "permission_attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "permission_retry_at": { - "name": "permission_retry_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "permission_last_served_at": { - "name": "permission_last_served_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "permission_started_at": { - "name": "permission_started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "permission_failure": { - "name": "permission_failure", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "kcgu_content_due_idx": { - "name": "kcgu_content_due_idx", - "columns": [ - { - "expression": "connector_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "generation_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "retry_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "last_served_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "kcgu_permission_due_idx": { - "name": "kcgu_permission_due_idx", - "columns": [ - { - "expression": "connector_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "generation_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "permission_retry_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "permission_last_served_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "knowledge_connector_google_user_connector_id_knowledge_connector_id_fk": { - "name": "knowledge_connector_google_user_connector_id_knowledge_connector_id_fk", - "tableFrom": "knowledge_connector_google_user", - "tableTo": "knowledge_connector", - "columnsFrom": ["connector_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "kcgu_pk": { - "name": "kcgu_pk", - "columns": ["connector_id", "user_id"] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "kcgu_status_check": { - "name": "kcgu_status_check", - "value": "\"knowledge_connector_google_user\".\"status\" IN ('pending', 'complete', 'blocked')" - }, - "kcgu_cursor_check": { - "name": "kcgu_cursor_check", - "value": "(\"knowledge_connector_google_user\".\"cursor\" IS NULL OR octet_length(\"knowledge_connector_google_user\".\"cursor\") <= 393216) AND (\"knowledge_connector_google_user\".\"permission_cursor\" IS NULL OR octet_length(\"knowledge_connector_google_user\".\"permission_cursor\") <= 393216)" - }, - "kcgu_attempts_check": { - "name": "kcgu_attempts_check", - "value": "\"knowledge_connector_google_user\".\"attempts\" >= 0 AND \"knowledge_connector_google_user\".\"permission_attempts\" >= 0" - } - }, - "isRLSEnabled": false - }, "public.knowledge_connector_member": { "name": "knowledge_connector_member", "schema": "", @@ -10309,6 +10089,228 @@ }, "isRLSEnabled": false }, + "public.knowledge_connector_partition": { + "name": "knowledge_connector_partition", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "partition_key": { + "name": "partition_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_id": { + "name": "generation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "retry_at": { + "name": "retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_served_at": { + "name": "last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure": { + "name": "failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "permission_cursor": { + "name": "permission_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_attempts": { + "name": "permission_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "permission_retry_at": { + "name": "permission_retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permission_last_served_at": { + "name": "permission_last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_started_at": { + "name": "permission_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_failure": { + "name": "permission_failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcp_content_due_idx": { + "name": "kcp_content_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcp_permission_due_idx": { + "name": "kcp_permission_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_partition_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_partition_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_partition", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kcp_pk": { + "name": "kcp_pk", + "columns": ["connector_id", "partition_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcp_partition_key_check": { + "name": "kcp_partition_key_check", + "value": "octet_length(\"knowledge_connector_partition\".\"partition_key\") BETWEEN 1 AND 1024" + }, + "kcp_context_check": { + "name": "kcp_context_check", + "value": "jsonb_typeof(\"knowledge_connector_partition\".\"context\") = 'object' AND octet_length(\"knowledge_connector_partition\".\"context\"::text) <= 16384" + }, + "kcp_status_check": { + "name": "kcp_status_check", + "value": "\"knowledge_connector_partition\".\"status\" IN ('pending', 'complete', 'blocked')" + }, + "kcp_cursor_check": { + "name": "kcp_cursor_check", + "value": "(\"knowledge_connector_partition\".\"cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"cursor\") <= 393216) AND (\"knowledge_connector_partition\".\"permission_cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"permission_cursor\") <= 393216)" + }, + "kcp_attempts_check": { + "name": "kcp_attempts_check", + "value": "\"knowledge_connector_partition\".\"attempts\" >= 0 AND \"knowledge_connector_partition\".\"permission_attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, "public.knowledge_connector_permission_grant": { "name": "knowledge_connector_permission_grant", "schema": "", diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index b011c96b2b6..032acfbe213 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2490,8 +2490,8 @@ { "idx": 356, "version": "7", - "when": 1789668694580, - "tag": "0356_google_company_user_progress", + "when": 1789672641125, + "tag": "0356_connector_partition_progress", "breakpoints": true } ] diff --git a/packages/db/schema.ts b/packages/db/schema.ts index fc628747528..0be0f42f8d2 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -5698,17 +5698,16 @@ export const knowledgeConnector = pgTable( }) ) -/** Bounded independent Google Workspace user continuations, committed with their owning listing checkpoint. */ -export const knowledgeConnectorGoogleUser = pgTable( - 'knowledge_connector_google_user', +/** Bounded provider partitions, committed atomically with their owning connector listing checkpoint. */ +export const knowledgeConnectorPartition = pgTable( + 'knowledge_connector_partition', { connectorId: text('connector_id') .notNull() .references(() => knowledgeConnector.id, { onDelete: 'cascade' }), - userId: text('user_id').notNull(), + partitionKey: text('partition_key').notNull(), generationId: text('generation_id').notNull(), - email: text('email').notNull(), - customerId: text('customer_id').notNull(), + context: jsonb('context').$type>().notNull(), cursor: text('cursor'), status: text('status').notNull().default('pending'), attempts: integer('attempts').notNull().default(0), @@ -5723,30 +5722,38 @@ export const knowledgeConnectorGoogleUser = pgTable( permissionFailure: jsonb('permission_failure').$type>(), }, (table) => ({ - pk: primaryKey({ name: 'kcgu_pk', columns: [table.connectorId, table.userId] }), - contentDueIdx: index('kcgu_content_due_idx').on( + pk: primaryKey({ name: 'kcp_pk', columns: [table.connectorId, table.partitionKey] }), + contentDueIdx: index('kcp_content_due_idx').on( table.connectorId, table.generationId, table.status, table.retryAt, table.lastServedAt ), - permissionDueIdx: index('kcgu_permission_due_idx').on( + permissionDueIdx: index('kcp_permission_due_idx').on( table.connectorId, table.generationId, table.permissionRetryAt, table.permissionLastServedAt ), + partitionKeyCheck: check( + 'kcp_partition_key_check', + sql`octet_length(${table.partitionKey}) BETWEEN 1 AND 1024` + ), + contextCheck: check( + 'kcp_context_check', + sql`jsonb_typeof(${table.context}) = 'object' AND octet_length(${table.context}::text) <= 16384` + ), statusCheck: check( - 'kcgu_status_check', + 'kcp_status_check', sql`${table.status} IN ('pending', 'complete', 'blocked')` ), cursorCheck: check( - 'kcgu_cursor_check', + 'kcp_cursor_check', sql`(${table.cursor} IS NULL OR octet_length(${table.cursor}) <= 393216) AND (${table.permissionCursor} IS NULL OR octet_length(${table.permissionCursor}) <= 393216)` ), attemptsCheck: check( - 'kcgu_attempts_check', + 'kcp_attempts_check', sql`${table.attempts} >= 0 AND ${table.permissionAttempts} >= 0` ), }) diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts index e246a16cebe..ee91d040c60 100644 --- a/packages/testing/src/mocks/schema.mock.ts +++ b/packages/testing/src/mocks/schema.mock.ts @@ -1740,24 +1740,23 @@ export const schemaMock = { subjectToken: 'knowledgeExternalGroupMember.subjectToken', createdAt: 'knowledgeExternalGroupMember.createdAt', }, - knowledgeConnectorGoogleUser: { - connectorId: 'knowledgeConnectorGoogleUser.connectorId', - userId: 'knowledgeConnectorGoogleUser.userId', - generationId: 'knowledgeConnectorGoogleUser.generationId', - email: 'knowledgeConnectorGoogleUser.email', - customerId: 'knowledgeConnectorGoogleUser.customerId', - cursor: 'knowledgeConnectorGoogleUser.cursor', - status: 'knowledgeConnectorGoogleUser.status', - attempts: 'knowledgeConnectorGoogleUser.attempts', - retryAt: 'knowledgeConnectorGoogleUser.retryAt', - lastServedAt: 'knowledgeConnectorGoogleUser.lastServedAt', - failure: 'knowledgeConnectorGoogleUser.failure', - permissionCursor: 'knowledgeConnectorGoogleUser.permissionCursor', - permissionAttempts: 'knowledgeConnectorGoogleUser.permissionAttempts', - permissionRetryAt: 'knowledgeConnectorGoogleUser.permissionRetryAt', - permissionLastServedAt: 'knowledgeConnectorGoogleUser.permissionLastServedAt', - permissionStartedAt: 'knowledgeConnectorGoogleUser.permissionStartedAt', - permissionFailure: 'knowledgeConnectorGoogleUser.permissionFailure', + knowledgeConnectorPartition: { + connectorId: 'knowledgeConnectorPartition.connectorId', + partitionKey: 'knowledgeConnectorPartition.partitionKey', + generationId: 'knowledgeConnectorPartition.generationId', + context: 'knowledgeConnectorPartition.context', + cursor: 'knowledgeConnectorPartition.cursor', + status: 'knowledgeConnectorPartition.status', + attempts: 'knowledgeConnectorPartition.attempts', + retryAt: 'knowledgeConnectorPartition.retryAt', + lastServedAt: 'knowledgeConnectorPartition.lastServedAt', + failure: 'knowledgeConnectorPartition.failure', + permissionCursor: 'knowledgeConnectorPartition.permissionCursor', + permissionAttempts: 'knowledgeConnectorPartition.permissionAttempts', + permissionRetryAt: 'knowledgeConnectorPartition.permissionRetryAt', + permissionLastServedAt: 'knowledgeConnectorPartition.permissionLastServedAt', + permissionStartedAt: 'knowledgeConnectorPartition.permissionStartedAt', + permissionFailure: 'knowledgeConnectorPartition.permissionFailure', }, knowledgeConnectorMember: { id: 'knowledgeConnectorMember.id', From ff9ddac1f01a76b8d7dccb3784a944177f56d6ac Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 17 Sep 2026 12:37:20 -0700 Subject: [PATCH 4/4] fix(search): clear unverifiable placeholder permissions --- .../google-workspace/company-work.ts | 1 + .../listing-continuation.integration.ts | 112 ++++++++++++++++-- .../knowledge/connectors/sync-content-pass.ts | 9 +- 3 files changed, 113 insertions(+), 9 deletions(-) diff --git a/apps/sim/connectors/google-workspace/company-work.ts b/apps/sim/connectors/google-workspace/company-work.ts index 75ca922326a..8e33d8ac2f8 100644 --- a/apps/sim/connectors/google-workspace/company-work.ts +++ b/apps/sim/connectors/google-workspace/company-work.ts @@ -7,6 +7,7 @@ export interface GoogleCompanyUserWork { cursor?: string } +/** Seeds per-user provider cursors and adopts legacy crawls without losing the active continuation. */ export interface GoogleCompanyCursorAdapter { seed: (user: GoogleCompanyUserWork['user']) => string /** Retains the exact active page when adopting the former single-user-at-a-time cursor. */ diff --git a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts index 8e6daf8dd7b..c24e2476960 100644 --- a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts @@ -94,7 +94,10 @@ import { } from '@/lib/knowledge/connectors/member-sync-engine' import { runConnectorContentPass } from '@/lib/knowledge/connectors/sync-content-pass' import { executeSync } from '@/lib/knowledge/connectors/sync-engine' -import { SOURCE_CONTENT_ERROR } from '@/lib/knowledge/connectors/sync-limits' +import { + SOURCE_CONTENT_ERROR, + SOURCE_PERMISSION_ERROR, +} from '@/lib/knowledge/connectors/sync-limits' import { createContentSyncLease, createMemberSyncLease } from '@/lib/knowledge/connectors/sync-lock' import { addDocument, persistDocumentAcls } from '@/lib/knowledge/connectors/sync-persistence' import * as documentService from '@/lib/knowledge/documents/service' @@ -240,11 +243,11 @@ describe('durable source and member cycles in PostgreSQL', () => { forceRehydrate: false, deadlineAt: Date.now() + 60_000, onPage: async (verified) => { - await persistDocumentAcls( + const write = await persistDocumentAcls( connectorId, new Map(verified.map((item) => [item.externalId, item.acl!])) ) - return { permissionsIncomplete: false } + return { permissionsIncomplete: write.rejected > 0 } }, }) expect(getDocument.mock.calls.map(([id]) => id).sort()).toEqual([ @@ -279,6 +282,101 @@ describe('durable source and member cycles in PostgreSQL', () => { }) }) + it.each([ + { description: 'a matching-hash body placeholder', storageKey: null, rejectAcl: false }, + { description: 'an unusable source ACL', storageKey: 'kb/verified-body.txt', rejectAcl: true }, + ])('clears stale permission evidence for $description', async ({ storageKey, rejectAcl }) => { + const connectorId = generateId() + const runId = generateId() + const externalId = 'permission-evidence' + const oldVerifiedAt = new Date(Date.now() - 25 * 60 * 60 * 1000) + const ownerAcl = [`u:${ids.aliceId}@fixture.test`] + await db.insert(knowledgeConnector).values({ + id: connectorId, + knowledgeBaseId: ids.knowledgeBaseId, + connectorType: 'google_drive', + status: 'syncing', + syncLockToken: runId, + accessMode: 'admin', + sourceConfig: {}, + }) + await db.insert(document).values({ + id: generateId(), + knowledgeBaseId: ids.knowledgeBaseId, + connectorId, + externalId, + filename: externalId, + fileUrl: '', + storageKey, + fileSize: storageKey === null ? 0 : 10, + mimeType: 'text/plain', + contentHash: `hash-${externalId}`, + processingStatus: storageKey === null ? 'failed' : 'completed', + processingError: storageKey === null ? 'Unsupported source file type' : null, + acl: ownerAcl, + aclRequirements: [ownerAcl, ['d:fixture.test']], + aclVerifiedAt: oldVerifiedAt, + sourceSeenAt: oldVerifiedAt, + }) + const listDocuments = vi.fn(async () => ({ + documents: [ + { + ...sourceDoc(externalId), + content: '', + contentDeferred: true, + acl: rejectAcl ? ['invalid-principal'] : ownerAcl, + }, + ], + hasMore: false, + permissionsOnly: true, + reconciliationSafe: false, + })) + const getDocument = vi.fn(async () => sourceDoc(externalId)) + const [connector] = await db + .select() + .from(knowledgeConnector) + .where(eq(knowledgeConnector.id, connectorId)) + const pass = await runConnectorContentPass({ + connectorId, + connector, + connectorConfig: { ...CONNECTOR_REGISTRY.google_drive, listDocuments }, + sourceConfig: {}, + syncContext: {}, + kbOwner: { userId: ids.aliceId, workspaceId: ids.workspaceId }, + billingAttribution: billing, + result: result(), + lease: createContentSyncLease(connectorId, runId), + leaseKind: 'content', + runId, + fingerprint: listingFingerprint({ source: 'permission-evidence' }), + documentAccess: 'admin', + getAccessToken: async () => 'fixture', + hydration: { getDocument }, + forceRehydrate: false, + deadlineAt: Date.now() + 60_000, + onPage: async (verified) => { + const write = await persistDocumentAcls( + connectorId, + new Map(verified.map((item) => [item.externalId, item.acl!])) + ) + return { permissionsIncomplete: write.rejected > 0 } + }, + }) + const [stored] = await db.select().from(document).where(eq(document.connectorId, connectorId)) + expect(stored).toMatchObject({ + acl: [], + aclRequirements: [], + aclVerifiedAt: null, + sourceSeenAt: oldVerifiedAt, + contentHash: `hash-${externalId}`, + storageKey, + deletedAt: null, + }) + expect(getDocument).not.toHaveBeenCalled() + expect(pass.checkpoint.permissionFailures).toBe(rejectAcl) + if (rejectAcl) expect(pass.holdNotice).toBe(SOURCE_PERMISSION_ERROR) + }) + it('preserves a permission-repaired document through the remaining content crawl and EOF reconciliation', async () => { const connectorId = generateId() const runId = generateId() @@ -378,11 +476,11 @@ describe('durable source and member cycles in PostgreSQL', () => { forceRehydrate: false, deadlineAt: Date.now() + 60_000, onPage: async (verified) => { - await persistDocumentAcls( + const write = await persistDocumentAcls( connectorId, new Map(verified.map((item) => [item.externalId, item.acl!])) ) - return { permissionsIncomplete: false } + return { permissionsIncomplete: write.rejected > 0 } }, }) return { pass, stats } @@ -520,11 +618,11 @@ describe('durable source and member cycles in PostgreSQL', () => { forceRehydrate: false, deadlineAt: Date.now() + 60_000, onPage: async (verified) => { - await persistDocumentAcls( + const write = await persistDocumentAcls( connectorId, new Map(verified.map((item) => [item.externalId, item.acl!])) ) - return { permissionsIncomplete: false } + return { permissionsIncomplete: write.rejected > 0 } }, }) expect(pass.complete).toBe(true) diff --git a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts index c11fb36b017..68b482446d1 100644 --- a/apps/sim/lib/knowledge/connectors/sync-content-pass.ts +++ b/apps/sim/lib/knowledge/connectors/sync-content-pass.ts @@ -163,9 +163,14 @@ export async function runConnectorContentPass(input: ContentPassInput) { if (page.permissionsOnly) { const changed = documents.filter((item) => { const prior = corpus.priorByExternalId.get(item.externalId) - return prior && (!prior.contentHash || prior.contentHash !== item.contentHash) + return ( + prior && + (!prior.contentHash || + prior.contentHash !== item.contentHash || + prior.storageKey === null) + ) }) - /** Revoke stale-body grants while retaining the content crawl's observation for EOF reconciliation. */ + /** Revoke grants without matching stored content, retaining the content crawl's observation for EOF reconciliation. */ if (changed.length) await withLease(async (tx) => { for (let offset = 0; offset < changed.length; offset += 500) {