diff --git a/apps/web/src/components/settings/EnvVars.tsx b/apps/web/src/components/settings/EnvVars.tsx index 6cd080a82..e5ada77d8 100644 --- a/apps/web/src/components/settings/EnvVars.tsx +++ b/apps/web/src/components/settings/EnvVars.tsx @@ -50,7 +50,8 @@ export function EnvVars() { } >

- Encrypted variables available to tasks in every environment. + Encrypted variables injected into tasks in every environment. Model + provider credentials are managed under Models.

{isPending ? ( diff --git a/apps/web/src/trpc/commands/environment-variables/index.test.ts b/apps/web/src/trpc/commands/environment-variables/index.test.ts index 37f0b3a35..55521ea1d 100644 --- a/apps/web/src/trpc/commands/environment-variables/index.test.ts +++ b/apps/web/src/trpc/commands/environment-variables/index.test.ts @@ -1,7 +1,8 @@ import type { UserAuthSuccess } from '@/types'; -const { mockFinalChain } = vi.hoisted(() => ({ +const { mockFinalChain, mockGetModelProviderNames } = vi.hoisted(() => ({ mockFinalChain: vi.fn(), + mockGetModelProviderNames: vi.fn().mockResolvedValue([]), })); vi.mock('@roomote/db/server', () => ({ @@ -20,6 +21,7 @@ vi.mock('@roomote/db/server', () => ({ inArray: vi.fn(), not: vi.fn(), getTableColumns: () => ({ id: 'env.id', name: 'env.name' }), + getPersistedModelProviderEnvironmentVariableNames: mockGetModelProviderNames, })); import { createEnvVarCommand, getEnvVarsCommand } from './index'; @@ -51,6 +53,7 @@ function buildMockAuth( describe('environment-variables commands', () => { beforeEach(() => { vi.clearAllMocks(); + mockGetModelProviderNames.mockResolvedValue([]); }); describe('getEnvVarsCommand', () => { @@ -67,6 +70,23 @@ describe('environment-variables commands', () => { expect(mockFinalChain).toHaveBeenCalledTimes(1); }); + + it('excludes model-provider and named OpenAI-compatible values', async () => { + mockFinalChain.mockResolvedValue([ + { id: 'task', name: 'MY_APP_TOKEN' }, + { id: 'model', name: 'TOGETHER_API_KEY' }, + { + id: 'custom-model', + name: 'OPENAI_COMPATIBLE_COMPANY_PROXY_API_KEY', + }, + { id: 'declared-custom', name: 'CUSTOM_LLM_TOKEN' }, + ]); + mockGetModelProviderNames.mockResolvedValue(['CUSTOM_LLM_TOKEN']); + + await expect(getEnvVarsCommand(buildMockAuth())).resolves.toEqual([ + { id: 'task', name: 'MY_APP_TOKEN' }, + ]); + }); }); describe('createEnvVarCommand', () => { @@ -91,5 +111,29 @@ describe('environment-variables commands', () => { 'is a reserved communications provider variable. Configure it under Settings → Communications.', ); }); + + it('reserves model-provider variable names during dual-write rollout', async () => { + await expect( + createEnvVarCommand(buildMockAuth(), { + name: 'TOGETHER_API_KEY', + value: 'task-key', + }), + ).rejects.toThrow( + 'is reserved for model-provider configuration during the compatibility rollout', + ); + }); + + it('reserves custom names declared by model-provider configuration', async () => { + mockGetModelProviderNames.mockResolvedValue(['CUSTOM_LLM_TOKEN']); + + await expect( + createEnvVarCommand(buildMockAuth(), { + name: 'CUSTOM_LLM_TOKEN', + value: 'task-key', + }), + ).rejects.toThrow( + 'is reserved for model-provider configuration during the compatibility rollout', + ); + }); }); }); diff --git a/apps/web/src/trpc/commands/environment-variables/index.ts b/apps/web/src/trpc/commands/environment-variables/index.ts index 6367e8222..41788e2a8 100644 --- a/apps/web/src/trpc/commands/environment-variables/index.ts +++ b/apps/web/src/trpc/commands/environment-variables/index.ts @@ -1,12 +1,14 @@ import { db, environmentVariables, + modelProviderEnvironmentVariables, eq, desc, inArray, not, getTableColumns, stringifyDecryptedEnvVarValue, + getPersistedModelProviderEnvironmentVariableNames, type DatabaseOrTransaction, } from '@roomote/db/server'; import { decryptSecrets } from '@roomote/db/encryption'; @@ -16,6 +18,8 @@ import { CONTROL_PLANE_ENV_VAR_NAMES, isAutoProvisionedComputeArtifactField, ROOMOTE_MANAGED_ENV_VAR_NAMES, + DEFAULT_MODEL_PROVIDER_ENV_KEYS, + isOpenAiCompatibleProviderEnvVarName, SOURCE_CONTROL_SECRET_ENV_VAR_NAMES, normalizePemEnvValue, } from '@roomote/types'; @@ -79,8 +83,28 @@ export async function getPersistedEnvironmentVariableValues( const PROVIDER_MANAGED_ENV_VAR_NAME_LIST = [ ...CONTROL_PLANE_ENV_VAR_NAMES, ...ROOMOTE_MANAGED_ENV_VAR_NAMES, + ...DEFAULT_MODEL_PROVIDER_ENV_KEYS, + 'R_MODEL_ENV_KEYS', ]; +function isStaticModelProviderEnvVarName(name: string): boolean { + return ( + DEFAULT_MODEL_PROVIDER_ENV_KEYS.includes(name) || + name === 'R_MODEL_ENV_KEYS' || + isOpenAiCompatibleProviderEnvVarName(name) + ); +} + +async function isModelProviderEnvVarName(name: string): Promise { + if (isStaticModelProviderEnvVarName(name)) { + return true; + } + + const modelProviderNames = + await getPersistedModelProviderEnvironmentVariableNames(); + return modelProviderNames.includes(name); +} + export async function upsertDeploymentEnvironmentVariables( tx: DatabaseOrTransaction, { @@ -148,6 +172,72 @@ export async function upsertDeploymentEnvironmentVariables( } } +/** + * Model settings dual-write to the dedicated store and the legacy deployment + * table for N-1 rollback compatibility. Remove the legacy write after the + * dedicated-store release is the oldest supported rollback target. + */ +export async function upsertModelProviderEnvironmentVariables( + tx: DatabaseOrTransaction, + { + userId, + values, + }: { + userId: string | null; + values: Array<{ name: string; value: string }>; + }, +) { + if (values.length === 0) { + return; + } + + await upsertDeploymentEnvironmentVariables(tx, { userId, values }); + + const names = Array.from(new Set(values.map((value) => value.name))); + const existingRows = await tx + .select({ + id: modelProviderEnvironmentVariables.id, + name: modelProviderEnvironmentVariables.name, + }) + .from(modelProviderEnvironmentVariables) + .where(inArray(modelProviderEnvironmentVariables.name, names)); + const existingByName = new Map(existingRows.map((row) => [row.name, row.id])); + const now = new Date(); + const valuesToInsert: Array<{ + name: string; + value: string; + createdByUserId: string | null; + lastUpdatedByUserId: string | null; + }> = []; + + for (const value of values) { + const normalizedValue = normalizePemEnvValue(value.value); + const existingId = existingByName.get(value.name); + + if (existingId) { + await tx + .update(modelProviderEnvironmentVariables) + .set({ + value: normalizedValue, + lastUpdatedByUserId: userId, + updatedAt: now, + }) + .where(eq(modelProviderEnvironmentVariables.id, existingId)); + } else { + valuesToInsert.push({ + name: value.name, + value: normalizedValue, + createdByUserId: userId, + lastUpdatedByUserId: userId, + }); + } + } + + if (valuesToInsert.length > 0) { + await tx.insert(modelProviderEnvironmentVariables).values(valuesToInsert); + } +} + export async function deleteDeploymentEnvironmentVariables( tx: DatabaseOrTransaction, names: string[], @@ -161,20 +251,51 @@ export async function deleteDeploymentEnvironmentVariables( .where(inArray(environmentVariables.name, [...new Set(names)])); } +export async function deleteModelProviderEnvironmentVariables( + tx: DatabaseOrTransaction, + names: string[], +) { + if (names.length === 0) { + return; + } + + const uniqueNames = [...new Set(names)]; + // Keep deleting both stores during the N-1 dual-write release. + await Promise.all([ + tx + .delete(modelProviderEnvironmentVariables) + .where(inArray(modelProviderEnvironmentVariables.name, uniqueNames)), + deleteDeploymentEnvironmentVariables(tx, uniqueNames), + ]); +} + export async function getEnvVarsCommand(auth: UserAuthSuccess) { assertAdmin(auth); const { value: _value, ...columns } = getTableColumns(environmentVariables); - return db - .select(columns) - .from(environmentVariables) - .where( - not( - inArray(environmentVariables.name, PROVIDER_MANAGED_ENV_VAR_NAME_LIST), - ), - ) - .orderBy(desc(environmentVariables.updatedAt)); + const [rows, modelProviderNames] = await Promise.all([ + db + .select(columns) + .from(environmentVariables) + .where( + not( + inArray( + environmentVariables.name, + PROVIDER_MANAGED_ENV_VAR_NAME_LIST, + ), + ), + ) + .orderBy(desc(environmentVariables.updatedAt)), + getPersistedModelProviderEnvironmentVariableNames(), + ]); + const modelProviderNameSet = new Set(modelProviderNames); + + return rows.filter( + (row) => + !modelProviderNameSet.has(row.name) && + !isStaticModelProviderEnvVarName(row.name), + ); } export async function deleteEnvVarCommand( @@ -193,6 +314,12 @@ export async function deleteEnvVarCommand( return { success: false as const, error: 'Environment variable not found' }; } + if (await isModelProviderEnvVarName(envVar.name)) { + throw new Error( + `"${envVar.name}" is managed under Settings → Models and cannot be deleted here.`, + ); + } + await db.transaction(async (tx) => { await tx .delete(environmentVariables) @@ -245,6 +372,12 @@ export async function createEnvVarCommand( throw new Error(`"${name}" is managed by Roomote and cannot be set here.`); } + if (await isModelProviderEnvVarName(name)) { + throw new Error( + `"${name}" is reserved for model-provider configuration during the compatibility rollout. Configure it under Settings → Models.`, + ); + } + const [existing] = await db .select() .from(environmentVariables) @@ -290,6 +423,12 @@ export async function updateEnvVarCommand( throw new Error('Environment variable not found'); } + if (await isModelProviderEnvVarName(envVar.name)) { + throw new Error( + `"${envVar.name}" is managed under Settings → Models and cannot be updated here.`, + ); + } + const [updatedEnvVar] = await db .update(environmentVariables) .set({ diff --git a/apps/web/src/trpc/commands/environment-variables/model-provider-environment-variables.test.ts b/apps/web/src/trpc/commands/environment-variables/model-provider-environment-variables.test.ts new file mode 100644 index 000000000..23cb032d1 --- /dev/null +++ b/apps/web/src/trpc/commands/environment-variables/model-provider-environment-variables.test.ts @@ -0,0 +1,65 @@ +import { + db, + environmentVariables, + modelProviderEnvironmentVariables, +} from '@roomote/db/server'; +import { decryptSecrets } from '@roomote/db/encryption'; + +import { + deleteModelProviderEnvironmentVariables, + upsertModelProviderEnvironmentVariables, +} from './index'; + +describe('model-provider environment-variable persistence', () => { + beforeEach(async () => { + await db.delete(modelProviderEnvironmentVariables); + await db.delete(environmentVariables); + }); + + it('dual-writes model values for N-1 rollback compatibility', async () => { + await db.transaction((tx) => + upsertModelProviderEnvironmentVariables(tx, { + userId: null, + values: [{ name: 'TOGETHER_API_KEY', value: ' together-key ' }], + }), + ); + + const [modelRows, legacyRows] = await Promise.all([ + db.select().from(modelProviderEnvironmentVariables), + db.select().from(environmentVariables), + ]); + + expect(modelRows).toHaveLength(1); + expect(modelRows[0]).toMatchObject({ + name: 'TOGETHER_API_KEY', + }); + await expect(decryptSecrets(modelRows[0]?.value)).resolves.toBe( + ' together-key ', + ); + expect(legacyRows).toHaveLength(1); + expect(legacyRows[0]).toMatchObject({ + name: 'TOGETHER_API_KEY', + }); + await expect(decryptSecrets(legacyRows[0]?.value)).resolves.toBe( + ' together-key ', + ); + }); + + it('deletes model values from both stores during the compatibility release', async () => { + await db.transaction(async (tx) => { + await upsertModelProviderEnvironmentVariables(tx, { + userId: null, + values: [{ name: 'TOGETHER_API_KEY', value: 'together-key' }], + }); + await deleteModelProviderEnvironmentVariables(tx, ['TOGETHER_API_KEY']); + }); + + const [modelRows, legacyRows] = await Promise.all([ + db.select().from(modelProviderEnvironmentVariables), + db.select().from(environmentVariables), + ]); + + expect(modelRows).toEqual([]); + expect(legacyRows).toEqual([]); + }); +}); diff --git a/apps/web/src/trpc/commands/setup-new/index.test.ts b/apps/web/src/trpc/commands/setup-new/index.test.ts index 1ac0123b6..05914f490 100644 --- a/apps/web/src/trpc/commands/setup-new/index.test.ts +++ b/apps/web/src/trpc/commands/setup-new/index.test.ts @@ -131,6 +131,10 @@ vi.mock('@roomote/db/server', () => ({ isChatGptSubscriptionConnected: vi.fn(async () => false), isGitHubCopilotSubscriptionConnected: vi.fn(async () => false), isXaiSubscriptionConnected: vi.fn(async () => false), + getPersistedModelProviderEnvironmentVariableNames: + mockGetPersistedEnvironmentVariableNames, + getPersistedModelProviderEnvironmentVariableValues: + mockGetPersistedEnvironmentVariableValues, })); vi.mock('@/lib/server', () => ({ @@ -178,8 +182,11 @@ vi.mock('@/lib/setup-new', () => ({ })); vi.mock('../environment-variables', () => ({ + deleteModelProviderEnvironmentVariables: vi.fn(), upsertDeploymentEnvironmentVariables: mockUpsertDeploymentEnvironmentVariables, + upsertModelProviderEnvironmentVariables: + mockUpsertDeploymentEnvironmentVariables, getPersistedEnvironmentVariableNames: mockGetPersistedEnvironmentVariableNames, getPersistedEnvironmentVariableValues: diff --git a/apps/web/src/trpc/commands/setup-new/index.ts b/apps/web/src/trpc/commands/setup-new/index.ts index 120e0488d..a0196e2ee 100644 --- a/apps/web/src/trpc/commands/setup-new/index.ts +++ b/apps/web/src/trpc/commands/setup-new/index.ts @@ -39,6 +39,8 @@ import { isChatGptSubscriptionConnected, isGitHubCopilotSubscriptionConnected, isXaiSubscriptionConnected, + getPersistedModelProviderEnvironmentVariableNames, + getPersistedModelProviderEnvironmentVariableValues, type DatabaseOrTransaction, } from '@roomote/db/server'; import { @@ -138,9 +140,11 @@ import { getSetupBaseStatus, } from '../setup/shared'; import { + deleteModelProviderEnvironmentVariables, getPersistedEnvironmentVariableNames, getPersistedEnvironmentVariableValues, upsertDeploymentEnvironmentVariables, + upsertModelProviderEnvironmentVariables, } from '../environment-variables'; import { assertTeamsBotCredentialsAuthenticate, @@ -1289,6 +1293,7 @@ export async function getSetupNewStatusCommand(auth: UserAuthSuccess) { persistedRuntimeModelConfig, persistedRuntimeComputeConfig, envVarNames, + modelEnvVarNames, nonSecretAuthEnvValues, nonSecretModelEnvValues, nonSecretComputeEnvValues, @@ -1302,8 +1307,9 @@ export async function getSetupNewStatusCommand(auth: UserAuthSuccess) { getPersistedRuntimeModelConfig(), getPersistedRuntimeComputeConfig(), getPersistedEnvironmentVariableNames(), + getPersistedModelProviderEnvironmentVariableNames(), getPersistedEnvironmentVariableValues([...NON_SECRET_AUTH_ENV_VAR_NAMES]), - getPersistedEnvironmentVariableValues( + getPersistedModelProviderEnvironmentVariableValues( SETUP_MODEL_PROVIDER_CATALOG.flatMap((provider) => [ ...(provider.authKind === 'endpoint' && provider.envVarName ? [provider.envVarName] @@ -1404,7 +1410,7 @@ export async function getSetupNewStatusCommand(auth: UserAuthSuccess) { const modelSetup = buildSetupModelStatus({ runtimeEnv: process.env, persistedModelConfig: persistedRuntimeModelConfig, - persistedEnvVarNames: envVarNames, + persistedEnvVarNames: modelEnvVarNames, persistedEnvVarValues: nonSecretModelEnvValues, selectedProvider: setupNewState.modelProvider, chatgptConnected, @@ -1550,7 +1556,7 @@ export async function saveSetupNewModelConfigCommand( const [currentState, persistedEnvVarNames, persistedTaskModelSettings] = await Promise.all([ getPersistedSetupNewState(tx), - getPersistedEnvironmentVariableNames(tx), + getPersistedModelProviderEnvironmentVariableNames(tx), getPersistedRawTaskModelSettings(tx), ]); const persistedEnvVarNameSet = new Set(persistedEnvVarNames); @@ -1568,7 +1574,7 @@ export async function saveSetupNewModelConfigCommand( }); if (credentialValues.length > 0) { - await upsertDeploymentEnvironmentVariables(tx, { + await upsertModelProviderEnvironmentVariables(tx, { userId, values: credentialValues, }); @@ -1581,14 +1587,10 @@ export async function saveSetupNewModelConfigCommand( ); if (clearedPersistedEnvVarNames.length > 0) { - await tx - .delete(environmentVariables) - .where( - and( - isNull(environmentVariables.userId), - inArray(environmentVariables.name, clearedPersistedEnvVarNames), - ), - ); + await deleteModelProviderEnvironmentVariables( + tx, + clearedPersistedEnvVarNames, + ); } } diff --git a/apps/web/src/trpc/commands/task-models/index.test.ts b/apps/web/src/trpc/commands/task-models/index.test.ts index 8f7e86542..034cf5ad1 100644 --- a/apps/web/src/trpc/commands/task-models/index.test.ts +++ b/apps/web/src/trpc/commands/task-models/index.test.ts @@ -58,15 +58,16 @@ vi.mock('@roomote/db/server', () => ({ isGitHubCopilotSubscriptionConnected: mockIsGitHubCopilotSubscriptionConnected, isXaiSubscriptionConnected: mockIsXaiSubscriptionConnected, + getPersistedModelProviderEnvironmentVariableNames: + mockGetPersistedEnvironmentVariableNames, + getPersistedModelProviderEnvironmentVariableValues: + mockGetPersistedEnvironmentVariableValues, isNull: vi.fn((column) => ({ isNull: column })), })); vi.mock('../environment-variables', () => ({ - getPersistedEnvironmentVariableNames: - mockGetPersistedEnvironmentVariableNames, - getPersistedEnvironmentVariableValues: - mockGetPersistedEnvironmentVariableValues, - upsertDeploymentEnvironmentVariables: + deleteModelProviderEnvironmentVariables: mockTxDelete, + upsertModelProviderEnvironmentVariables: mockUpsertDeploymentEnvironmentVariables, })); @@ -1159,7 +1160,6 @@ describe('task model provider commands', () => { onConflictDoUpdate: txOnConflictDoUpdate, })); const txInsert = vi.fn(() => ({ values: txValues })); - const txDeleteWhere = vi.fn(async () => undefined); function buildSelectChainMock(rows: unknown[]) { return { @@ -1191,7 +1191,6 @@ describe('task model provider commands', () => { beforeEach(() => { vi.clearAllMocks(); - mockTxDelete.mockReturnValue({ where: txDeleteWhere }); for (const name of PROVIDER_ENV_VAR_NAMES) { originalEnvValues.set(name, process.env[name]); @@ -1636,13 +1635,9 @@ describe('task model provider commands', () => { }); expect(mockUpsertDeploymentEnvironmentVariables).not.toHaveBeenCalled(); - expect(mockTxDelete).toHaveBeenCalled(); - expect(txDeleteWhere).toHaveBeenCalledWith({ - and: [ - { isNull: 'env.user_id' }, - { column: 'env.name', values: ['AWS_REGION'] }, - ], - }); + expect(mockTxDelete).toHaveBeenCalledWith(expect.anything(), [ + 'AWS_REGION', + ]); }); it('does not delete anything when a blanked optional field was never saved', async () => { @@ -1729,9 +1724,9 @@ describe('task model provider commands', () => { provider: 'xai', }); - const { inArray } = await import('@roomote/db/server'); - expect(mockTxDelete).toHaveBeenCalled(); - expect(inArray).toHaveBeenCalledWith('env.name', ['XAI_API_KEY']); + expect(mockTxDelete).toHaveBeenCalledWith(expect.anything(), [ + 'XAI_API_KEY', + ]); // Models and runtime stay; only the key was removed. expect(txOnConflictDoUpdate).not.toHaveBeenCalled(); }); @@ -1809,10 +1804,9 @@ describe('task model provider commands', () => { provider: 'anthropic', }); - const { inArray, isNull } = await import('@roomote/db/server'); - expect(mockTxDelete).toHaveBeenCalled(); - expect(inArray).toHaveBeenCalledWith('env.name', ['ANTHROPIC_API_KEY']); - expect(isNull).toHaveBeenCalledWith('env.user_id'); + expect(mockTxDelete).toHaveBeenCalledWith(expect.anything(), [ + 'ANTHROPIC_API_KEY', + ]); const updateSet = txOnConflictDoUpdate.mock.calls[0]?.[0]?.set; expect(updateSet.taskModelSettings.models).toEqual([ diff --git a/apps/web/src/trpc/commands/task-models/index.ts b/apps/web/src/trpc/commands/task-models/index.ts index 771a9c5d5..51870c5d6 100644 --- a/apps/web/src/trpc/commands/task-models/index.ts +++ b/apps/web/src/trpc/commands/task-models/index.ts @@ -1,14 +1,12 @@ import { - and, db, deploymentSettings, - environmentVariables, eq, - inArray, + getPersistedModelProviderEnvironmentVariableNames, + getPersistedModelProviderEnvironmentVariableValues, isChatGptSubscriptionConnected, isGitHubCopilotSubscriptionConnected, isXaiSubscriptionConnected, - isNull, type DatabaseOrTransaction, } from '@roomote/db/server'; import { @@ -52,9 +50,8 @@ import { getDeploymentTaskModelSettings, } from '@/lib/server/task-models'; import { - getPersistedEnvironmentVariableValues, - getPersistedEnvironmentVariableNames, - upsertDeploymentEnvironmentVariables, + deleteModelProviderEnvironmentVariables, + upsertModelProviderEnvironmentVariables, } from '../environment-variables'; import { fetchModelsDevCatalog, @@ -303,7 +300,7 @@ export async function getTaskModelSettingsCommand( ] = await Promise.all([ getDeploymentTaskModelSettings(), getDeploymentRuntimeModelConfig(), - getPersistedEnvironmentVariableNames(), + getPersistedModelProviderEnvironmentVariableNames(), isChatGptSubscriptionConnected(), isGitHubCopilotSubscriptionConnected(), isXaiSubscriptionConnected(), @@ -421,7 +418,7 @@ export async function getTaskModelProviderSetupCommand( xaiSubscriptionConnected, ] = await Promise.all([ getDeploymentRuntimeModelConfig(), - getPersistedEnvironmentVariableNames(), + getPersistedModelProviderEnvironmentVariableNames(), getDeploymentSetupNewState(), isChatGptSubscriptionConnected(), isGitHubCopilotSubscriptionConnected(), @@ -453,7 +450,7 @@ export async function getTaskModelProviderSetupCommand( ); const persistedAdditionalEnvValues = - await getPersistedEnvironmentVariableValues([ + await getPersistedModelProviderEnvironmentVariableValues([ ...new Set([...catalogNonSecretEnvNames, ...openAiCompatibleEnvNames]), ]); @@ -489,7 +486,7 @@ export async function autoAddConnectedSubscriptionTaskModels( return db.transaction(async (tx) => { const [persistedEnvVarNames, persistedTaskModels] = await Promise.all([ - getPersistedEnvironmentVariableNames(tx), + getPersistedModelProviderEnvironmentVariableNames(tx), getPersistedRawTaskModelSettings(tx), ]); const connectedProviderIds = new Set([ @@ -638,7 +635,7 @@ export async function saveTaskModelProviderCommand( const [currentSetupNewState, persistedEnvVarNames, persistedTaskModels] = await Promise.all([ getDeploymentSetupNewState(tx), - getPersistedEnvironmentVariableNames(tx), + getPersistedModelProviderEnvironmentVariableNames(tx), getPersistedRawTaskModelSettings(tx), ]); const persistedEnvVarNameSet = new Set(persistedEnvVarNames); @@ -654,7 +651,7 @@ export async function saveTaskModelProviderCommand( }); if (credentialValues.length > 0) { - await upsertDeploymentEnvironmentVariables(tx, { + await upsertModelProviderEnvironmentVariables(tx, { userId: auth.userId, values: credentialValues, }); @@ -667,14 +664,10 @@ export async function saveTaskModelProviderCommand( ); if (clearedPersistedEnvVarNames.length > 0) { - await tx - .delete(environmentVariables) - .where( - and( - isNull(environmentVariables.userId), - inArray(environmentVariables.name, clearedPersistedEnvVarNames), - ), - ); + await deleteModelProviderEnvironmentVariables( + tx, + clearedPersistedEnvVarNames, + ); } const connectedProviderIds = new Set([ @@ -911,7 +904,7 @@ export async function deleteTaskModelProviderCommand( persistedTaskModelSettings, ] = await Promise.all([ getDeploymentRuntimeModelConfig(), - getPersistedEnvironmentVariableNames(tx), + getPersistedModelProviderEnvironmentVariableNames(tx), getDeploymentSetupNewState(tx), isChatGptSubscriptionConnected(), isXaiSubscriptionConnected(), @@ -954,14 +947,7 @@ export async function deleteTaskModelProviderCommand( const providerEnvVarNames = getSetupModelProviderEnvVarNames(provider); if (providerEnvVarNames.length > 0) { - await tx - .delete(environmentVariables) - .where( - and( - isNull(environmentVariables.userId), - inArray(environmentVariables.name, providerEnvVarNames), - ), - ); + await deleteModelProviderEnvironmentVariables(tx, providerEnvVarNames); } if (xaiKeyOnlyDelete) { @@ -1018,7 +1004,7 @@ export async function getLaunchTaskModelsCommand(_auth: UserAuthSuccess) { isChatGptSubscriptionConnected(), isGitHubCopilotSubscriptionConnected(), isXaiSubscriptionConnected(), - getPersistedEnvironmentVariableNames(), + getPersistedModelProviderEnvironmentVariableNames(), ]); const providerSetup = buildSetupModelStatus({ runtimeEnv: process.env, @@ -1514,8 +1500,11 @@ export async function lookupTaskModelCommand( const runtimeOpenRouterKey = process.env.OPENROUTER_API_KEY?.trim(); const openRouterKey = runtimeOpenRouterKey ? runtimeOpenRouterKey - : (await getPersistedEnvironmentVariableValues(['OPENROUTER_API_KEY'])) - .OPENROUTER_API_KEY; + : ( + await getPersistedModelProviderEnvironmentVariableValues([ + 'OPENROUTER_API_KEY', + ]) + ).OPENROUTER_API_KEY; if (!openRouterKey) { return { diff --git a/apps/web/src/trpc/commands/task-models/local-provider-discovery.test.ts b/apps/web/src/trpc/commands/task-models/local-provider-discovery.test.ts index 090b5d0a3..0cb06079a 100644 --- a/apps/web/src/trpc/commands/task-models/local-provider-discovery.test.ts +++ b/apps/web/src/trpc/commands/task-models/local-provider-discovery.test.ts @@ -9,8 +9,8 @@ const { mockGetPersistedEnvironmentVariableValues } = vi.hoisted(() => ({ mockGetPersistedEnvironmentVariableValues: vi.fn(), })); -vi.mock('../environment-variables', () => ({ - getPersistedEnvironmentVariableValues: +vi.mock('@roomote/db/server', () => ({ + getPersistedModelProviderEnvironmentVariableValues: mockGetPersistedEnvironmentVariableValues, })); diff --git a/apps/web/src/trpc/commands/task-models/local-provider-discovery.ts b/apps/web/src/trpc/commands/task-models/local-provider-discovery.ts index 7935f4357..92db76be2 100644 --- a/apps/web/src/trpc/commands/task-models/local-provider-discovery.ts +++ b/apps/web/src/trpc/commands/task-models/local-provider-discovery.ts @@ -9,8 +9,8 @@ import { type SetupModelProviderId, type TaskModelMetadata, } from '@roomote/types'; +import { getPersistedModelProviderEnvironmentVariableValues } from '@roomote/db/server'; -import { getPersistedEnvironmentVariableValues } from '../environment-variables'; import { mergeMetadata } from './models-dev'; const LOCAL_PROVIDER_REQUEST_TIMEOUT_MS = 15_000; @@ -260,7 +260,7 @@ async function resolveLocalProviderConnection( input?: LocalProviderConnectionInput, ): Promise { const envNames = getLocalProviderConnectionEnv(provider); - const persisted = await getPersistedEnvironmentVariableValues([ + const persisted = await getPersistedModelProviderEnvironmentVariableValues([ envNames.baseUrl, ...(envNames.apiKey ? [envNames.apiKey] : []), ]); diff --git a/apps/worker/src/commands/snapshot.ts b/apps/worker/src/commands/snapshot.ts index d65989536..109afe8cc 100644 --- a/apps/worker/src/commands/snapshot.ts +++ b/apps/worker/src/commands/snapshot.ts @@ -60,7 +60,7 @@ export async function snapshot({ gitHubToken: GH_TOKEN, sourceControlToken, taskId, - } = await sdk.taskRuns.fetchSnapshotEnv({ runId }); + } = await sdk.taskRuns.fetchSnapshotEnv({ runId, envContractVersion: 2 }); const envVars: Record = { ...fetchedEnvVars, diff --git a/apps/worker/src/commands/utils/execute-task-run.test.ts b/apps/worker/src/commands/utils/execute-task-run.test.ts index 75b0cc0d1..376807c88 100644 --- a/apps/worker/src/commands/utils/execute-task-run.test.ts +++ b/apps/worker/src/commands/utils/execute-task-run.test.ts @@ -151,6 +151,7 @@ describe('executeTaskRun', () => { })), }); resolveWorkerReleaseMetadataMock.mockReturnValue({ + envContractVersion: 2, workerReleaseTag: 'worker-v1.2.3', workerVersion: '1.2.3', workerCommit: 'abc123', @@ -182,6 +183,8 @@ describe('executeTaskRun', () => { }, envVars: { FOO: 'bar', + R_MODEL_ENV_KEYS: 'ANTHROPIC_API_KEY', + ANTHROPIC_API_KEY: 'model-secret', }, gitAuthor: { name: 'Chris', @@ -234,6 +237,22 @@ describe('executeTaskRun', () => { expect(setupArgs.workspace.envVars).toMatchObject({ FOO: 'bar', }); + expect(setupArgs.workspace.envVars).not.toHaveProperty('ANTHROPIC_API_KEY'); + expect(injectEnvVarsMock).toHaveBeenCalledWith( + { FOO: 'bar' }, + expect.anything(), + expect.anything(), + ); + expect(runFn).toHaveBeenCalledWith( + expect.objectContaining({ + jobContext: expect.objectContaining({ + modelRuntimeEnv: expect.objectContaining({ + ANTHROPIC_API_KEY: 'model-secret', + }), + }), + userEnvVars: { FOO: 'bar' }, + }), + ); expect(typeof setupArgs.recordPhase).toBe('function'); expect(sdkTaskRunsStampMilestoneMock).toHaveBeenCalledWith({ runId: 42, @@ -871,13 +890,14 @@ describe('executeTaskRun', () => { }); expect(fetchFn).toHaveBeenCalledWith(42, { + envContractVersion: 2, workerReleaseTag: 'worker-v1.2.3', workerVersion: '1.2.3', workerCommit: 'abc123', }); }); - it('preserves operator-provided model provider env vars', async () => { + it('extracts model provider values from legacy flat responses', async () => { const runFn = vi.fn().mockResolvedValue({ status: RunStatus.Idle, }); @@ -920,14 +940,21 @@ describe('executeTaskRun', () => { const setupArgs = setupMock.mock.calls[0]?.[0]; expect(setupArgs.workspace.userEnvVars).toEqual({ FOO: 'bar', - OPENAI_API_KEY: 'sk-original-user-key', OPENAI_BASE_URL: 'https://api.openai.com/v1', }); expect(setupArgs.workspace.envVars).toMatchObject({ FOO: 'bar', - OPENAI_API_KEY: 'sk-original-user-key', OPENAI_BASE_URL: 'https://api.openai.com/v1', }); + expect(runFn).toHaveBeenCalledWith( + expect.objectContaining({ + jobContext: expect.objectContaining({ + modelRuntimeEnv: expect.objectContaining({ + OPENAI_API_KEY: 'sk-original-user-key', + }), + }), + }), + ); }); it('starts the worker heartbeat before setup begins', async () => { diff --git a/apps/worker/src/commands/utils/execute-task-run.ts b/apps/worker/src/commands/utils/execute-task-run.ts index f08420aa7..3bc2b2c36 100644 --- a/apps/worker/src/commands/utils/execute-task-run.ts +++ b/apps/worker/src/commands/utils/execute-task-run.ts @@ -50,12 +50,14 @@ import { import { BackgroundEnvironmentSetupController } from './background-environment-setup-controller'; import { injectEnvVars, writeBashrc } from './env-vars'; +import { splitLegacyModelRuntimeEnv } from '../../run-task/env'; import { buildServiceContextForPreviewProxy } from './service-context'; import { finalizeJob, handleTaskRunError } from './task-run-lifecycle'; interface PreparedTaskRunBase { taskRun: TaskRun; envVars: Record; + modelRuntimeEnv?: Record; sourceControlToken?: SourceControlTokenMetadata; gitAuthor?: { name: string; email: string }; setupOnboardingTask?: boolean; @@ -339,7 +341,19 @@ export async function executeTaskRun({ return false; } - const { envVars } = jobContext; + const resolvedEnv = + jobContext.modelRuntimeEnv === undefined + ? splitLegacyModelRuntimeEnv(jobContext.envVars) + : { + envVars: jobContext.envVars, + modelRuntimeEnv: jobContext.modelRuntimeEnv, + }; + const { envVars, modelRuntimeEnv } = resolvedEnv; + const effectiveJobContext = { + ...jobContext, + envVars, + modelRuntimeEnv, + } as TPrepared; taskRun = jobContext.taskRun; const runIdForEvents = taskRun.id; callbacks = mergeRunTaskCallbacks( @@ -495,6 +509,7 @@ export async function executeTaskRun({ }); workerEnv.setRuntimeEnv(envVars); + workerEnv.setModelRuntimeEnv?.(modelRuntimeEnv); const serviceContext = buildServiceContextForPreviewProxy( taskRun, @@ -665,7 +680,7 @@ export async function executeTaskRun({ await backgroundEnvironmentSetupController.preflightTaskStart(); const runTaskPromise = runFn({ - jobContext, + jobContext: effectiveJobContext, userEnvVars, workspace, workspacePath, diff --git a/apps/worker/src/env/worker-env.ts b/apps/worker/src/env/worker-env.ts index a32058c31..29a10db74 100644 --- a/apps/worker/src/env/worker-env.ts +++ b/apps/worker/src/env/worker-env.ts @@ -143,6 +143,9 @@ export class WorkerEnv { */ private runtimeEnv: Record = {}; + /** Reloadable model values that are scoped to the agent harness. */ + private modelRuntimeEnv: Record = {}; + /** * Launcher-supplied model config and provider keys. These are stable * across deployment env reloads because they describe how the harness starts, @@ -363,6 +366,19 @@ export class WorkerEnv { return { ...this.runtimeEnv }; } + setModelRuntimeEnv(vars: Record): void { + this.modelRuntimeEnv = {}; + for (const [key, value] of Object.entries(vars)) { + if (value !== undefined) { + this.modelRuntimeEnv[key] = value; + } + } + } + + getModelRuntimeEnv(): Record { + return { ...this.modelRuntimeEnv }; + } + /** Set a single system base var. */ setSystemBase(key: string, value: string): void { this.systemBase[key] = value; diff --git a/apps/worker/src/monitoring/worker-release-metadata.ts b/apps/worker/src/monitoring/worker-release-metadata.ts index 725864846..058e1d08f 100644 --- a/apps/worker/src/monitoring/worker-release-metadata.ts +++ b/apps/worker/src/monitoring/worker-release-metadata.ts @@ -6,6 +6,7 @@ const INSTALLED_WORKER_COMMIT_FILE = '/sandbox/worker/COMMIT'; const INSTALLED_WORKER_RELEASE_TAG_FILE = '/sandbox/worker/WORKER_RELEASE_TAG'; export interface WorkerReleaseMetadata { + envContractVersion?: number; sentryRelease?: string; workerCommit?: string; workerReleaseTag?: string; @@ -97,6 +98,7 @@ export function resolveWorkerReleaseMetadata( const fallbackWorkerRelease = resolveFallbackWorkerRelease(env); return { + envContractVersion: 2, sentryRelease: workerReleaseTag || fallbackWorkerRelease || installedWorkerCommit, workerCommit: installedWorkerCommit, diff --git a/apps/worker/src/run-task/__tests__/create-harness.test.ts b/apps/worker/src/run-task/__tests__/create-harness.test.ts index 267383b56..8f839924c 100644 --- a/apps/worker/src/run-task/__tests__/create-harness.test.ts +++ b/apps/worker/src/run-task/__tests__/create-harness.test.ts @@ -437,6 +437,10 @@ describe('createHarness', () => { url: 'https://actor-b.example/mcp', }, }; + result.harness.setCommandEnv?.({ + BASH_ENV: '/home/testuser/.roomote/env.sh', + OPENAI_API_KEY: 'sk-fresh-openai', + }); await result.harness.requestReconnect?.({ reason: 'actor-scoped MCP refresh for actor-b', @@ -448,7 +452,7 @@ describe('createHarness', () => { workspacePath: '/tmp/workspace', runtimeEnv: { BASH_ENV: '/home/testuser/.roomote/env.sh', - OPENAI_API_KEY: 'sk-test-openai', + OPENAI_API_KEY: 'sk-fresh-openai', }, cancelSignal: expect.any(AbortSignal), logger: expect.any(Object), diff --git a/apps/worker/src/run-task/__tests__/run-task.test.ts b/apps/worker/src/run-task/__tests__/run-task.test.ts index b26fc2c9a..bfcc03856 100644 --- a/apps/worker/src/run-task/__tests__/run-task.test.ts +++ b/apps/worker/src/run-task/__tests__/run-task.test.ts @@ -3667,7 +3667,7 @@ describe('runTask', () => { visibleInTranscript: false, }); }); - it('passes operator model config through the OpenCode runtime env', async () => { + it('passes harness-scoped model config through the OpenCode runtime env', async () => { buildSandboxInstructionMock.mockReturnValue(undefined as never); await runTask({ @@ -3679,9 +3679,11 @@ describe('runTask', () => { payload: {}, result: null, } as never, - envVars: { + envVars: {}, + modelRuntimeEnv: { R_MODEL: 'provider-id/model-id', R_SMALL_MODEL: 'provider-id/small-model-id', + ANTHROPIC_API_KEY: 'model-secret', }, workspacePath: '/tmp/workspace', prompt: '', @@ -3716,6 +3718,7 @@ describe('runTask', () => { runtimeEnv: expect.objectContaining({ R_MODEL: 'provider-id/model-id', R_SMALL_MODEL: 'provider-id/small-model-id', + ANTHROPIC_API_KEY: 'model-secret', }), }), ); diff --git a/apps/worker/src/run-task/create-harness.ts b/apps/worker/src/run-task/create-harness.ts index aca24970a..1b6604ef6 100644 --- a/apps/worker/src/run-task/create-harness.ts +++ b/apps/worker/src/run-task/create-harness.ts @@ -74,7 +74,7 @@ export async function createHarness({ logger, prepareQueuedPromptActorScope, }: CreateHarnessOptions): Promise { - const harnessCommandEnv = buildHarnessCommandEnv(runtimeEnv); + let harnessCommandEnv = buildHarnessCommandEnv(runtimeEnv); const stampHarnessStarted = () => { // Best-effort: do not derail task startup on telemetry failures. void sdk.taskRuns @@ -170,6 +170,9 @@ export async function createHarness({ logger, spawnHarness, diagnosticEvents, + onCommandEnvChanged: (env) => { + harnessCommandEnv = buildHarnessCommandEnv(env); + }, }); await reconnectableHarness.start({ initialSessionId: harnessSessionId }); stampHarnessStarted(); diff --git a/apps/worker/src/run-task/env.ts b/apps/worker/src/run-task/env.ts index fcc916fef..6652ed726 100644 --- a/apps/worker/src/run-task/env.ts +++ b/apps/worker/src/run-task/env.ts @@ -93,3 +93,17 @@ export function buildOpenCodeHarnessEnv( return harnessEnv; } + +export function splitLegacyModelRuntimeEnv(env: Record): { + envVars: Record; + modelRuntimeEnv: Record; +} { + const modelRuntimeEnv = buildOpenCodeHarnessEnv(env); + const envVars = { ...env }; + + for (const name of Object.keys(modelRuntimeEnv)) { + delete envVars[name]; + } + + return { envVars, modelRuntimeEnv }; +} diff --git a/apps/worker/src/run-task/reconnectable-harness.ts b/apps/worker/src/run-task/reconnectable-harness.ts index 4587ec45c..688b25d82 100644 --- a/apps/worker/src/run-task/reconnectable-harness.ts +++ b/apps/worker/src/run-task/reconnectable-harness.ts @@ -48,6 +48,7 @@ interface ReconnectableHarnessConfig { maxReconnectAttempts?: number; /** Durable breadcrumbs for harness lifecycle transitions; observer-only. */ diagnosticEvents?: DiagnosticEventRecorder; + onCommandEnvChanged?: (env: Record) => void; } interface BoundHarnessListeners { @@ -116,6 +117,9 @@ export class ReconnectableHarness private readonly spawnHarness: ReconnectableHarnessConfig['spawnHarness']; private readonly maxReconnectAttempts: number; private readonly diagnosticEvents: DiagnosticEventRecorder | undefined; + private readonly onCommandEnvChanged: + | ((env: Record) => void) + | undefined; private currentHarness: Harness | null = null; private currentSubprocess: ResultPromise | null = null; @@ -138,6 +142,7 @@ export class ReconnectableHarness this.maxReconnectAttempts = config.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS; this.diagnosticEvents = config.diagnosticEvents; + this.onCommandEnvChanged = config.onCommandEnvChanged; } async start(options?: { initialSessionId?: string }): Promise { @@ -252,6 +257,7 @@ export class ReconnectableHarness setCommandEnv(env: Record): void { this.currentCommandEnv = env; + this.onCommandEnvChanged?.(env); this.currentHarness?.setCommandEnv?.(env); } diff --git a/apps/worker/src/run-task/run-task.ts b/apps/worker/src/run-task/run-task.ts index 0410e741d..1f6fc6022 100644 --- a/apps/worker/src/run-task/run-task.ts +++ b/apps/worker/src/run-task/run-task.ts @@ -2,6 +2,7 @@ import { type CommunicationProvider, type AcpRequestUserInputAnswers, buildInferenceGatewayUrl, + DEFAULT_MODEL_PROVIDER_CREDENTIAL_ENV_VAR_NAMES, DISABLED_MODEL_PROVIDER_ENV_VAR_NAMES, INFERENCE_GATEWAY_CHATGPT_ENV_VAR_NAME, INFERENCE_GATEWAY_GITHUB_COPILOT_ENV_VAR_NAME, @@ -11,6 +12,7 @@ import { isTaskModelIdDisabled, OPENCODE_AUTH_CONTENT_ENV_VAR_NAME, parseInferenceGatewayKeys, + parseModelProviderEnvKeys, RunStatus, TaskPayloadKind, type QueuedCommunicationMessage, @@ -652,6 +654,7 @@ function getQueuedSnapshotResumeLinearMessages( export const runTask = async ({ taskRun, envVars, + modelRuntimeEnv, userEnvVars, workspacePath, usesSharedWorkspaceRoot, @@ -717,7 +720,21 @@ export const runTask = async ({ : { ...process.env, ...envVars }; const sanitizedEnv = sanitizeEnv(unsanitizedEnv); - const openCodeHarnessEnv = buildOpenCodeHarnessEnv(unsanitizedEnv); + const openCodeHarnessEnv = buildOpenCodeHarnessEnv({ + ...unsanitizedEnv, + ...modelRuntimeEnv, + }); + const harnessOnlySecretNames = [ + ...new Set([ + ...DEFAULT_MODEL_PROVIDER_CREDENTIAL_ENV_VAR_NAMES, + ...parseModelProviderEnvKeys(modelRuntimeEnv?.R_MODEL_ENV_KEYS), + ]), + ].filter((name) => modelRuntimeEnv?.[name] !== undefined); + + if (harnessOnlySecretNames.length > 0) { + openCodeHarnessEnv.ROOMOTE_HARNESS_ONLY_SECRET_NAMES = + harnessOnlySecretNames.join(','); + } // Org env vars (from the dequeue payload) are merged BEFORE sanitizeEnv // so that system-critical vars (HOME, PATH, GH_TOKEN, etc.) from the diff --git a/apps/worker/src/run-task/types.ts b/apps/worker/src/run-task/types.ts index be257b75a..8594cdf48 100644 --- a/apps/worker/src/run-task/types.ts +++ b/apps/worker/src/run-task/types.ts @@ -154,6 +154,8 @@ export type RunTaskCallbacks = { export type RunTaskOptions = { taskRun: DequeuedTaskRun['taskRun']; envVars: Record; + /** Model configuration and credentials scoped to the agent harness. */ + modelRuntimeEnv?: Record; /** * Snapshot of the dequeue-provided env vars taken before injectEnvVars adds * runtime-internal entries (auth bypass values, BASH_ENV, ...). This is the diff --git a/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server-bootstrap.test.ts b/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server-bootstrap.test.ts index d2b1d76e5..a86e73325 100644 --- a/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server-bootstrap.test.ts +++ b/apps/worker/src/sandbox-server/lib/harnesses/__tests__/opencode-server-bootstrap.test.ts @@ -2026,6 +2026,41 @@ describe('opencode-server bootstrap', () => { expect(commandEnv.MISTRAL_API_KEY).toBeUndefined(); }); + it('keeps harness-only credentials on OpenCode but removes them from child shells', async () => { + const { prepareOpenCodeCommandEnv } = + await import('../opencode-server/bootstrap'); + const homeDir = createTempHome(); + const sharedBashEnvPath = path.join(homeDir, 'roomote-env.sh'); + fs.writeFileSync(sharedBashEnvPath, ''); + + const { commandEnv } = await prepareOpenCodeCommandEnv({ + runtimeEnv: { + ...createDirectHarnessRuntimeEnv(homeDir), + BASH_ENV: sharedBashEnvPath, + OPENAI_API_KEY: 'model-secret', + ROOMOTE_HARNESS_ONLY_SECRET_NAMES: 'OPENAI_API_KEY', + }, + workspacePath: '/tmp/workspace', + logger: createLogger(), + }); + + expect(commandEnv.OPENAI_API_KEY).toBe('model-secret'); + expect(commandEnv.ROOMOTE_HARNESS_ONLY_SECRET_NAMES).toBeUndefined(); + expect( + execFileSync( + 'bash', + [ + '-lc', + `printf "%s|" "$OPENAI_API_KEY"; bash -lc 'printf "%s" "$OPENAI_API_KEY"'`, + ], + { + env: commandEnv, + encoding: 'utf8', + }, + ), + ).toBe('model-secret|'); + }); + it('completes OpenCode plugin seed for the resolved version without network access', async () => { const { prepareOpenCodeCommandEnv } = await import('../opencode-server/bootstrap'); diff --git a/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/bootstrap.ts b/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/bootstrap.ts index 65f4cfae0..eae76829a 100644 --- a/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/bootstrap.ts +++ b/apps/worker/src/sandbox-server/lib/harnesses/opencode-server/bootstrap.ts @@ -211,6 +211,13 @@ export async function prepareOpenCodeCommandEnv(options: { logger: HarnessLogger; }): Promise<{ commandEnv: Record; model?: string }> { const commandEnv = normalizeOpenCodeRuntimeEnv(options.runtimeEnv); + const harnessOnlySecretNames = ( + commandEnv.ROOMOTE_HARNESS_ONLY_SECRET_NAMES ?? '' + ) + .split(',') + .map((name) => name.trim()) + .filter((name) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(name)); + delete commandEnv.ROOMOTE_HARNESS_ONLY_SECRET_NAMES; const parsedMcpServers = Object.fromEntries( Object.entries(options.mcpServers ?? {}).flatMap(([name, config]) => { const parsedConfig = parseDirectMcpConfig(config); @@ -269,7 +276,11 @@ export async function prepareOpenCodeCommandEnv(options: { logger: options.logger, }); - await materializeOpenCodeBashEnvOverlay({ commandEnv, homeDir }); + await materializeOpenCodeBashEnvOverlay({ + commandEnv, + homeDir, + harnessOnlySecretNames, + }); // OpenCode always Arborist-installs @opencode-ai/plugin into the config dir // and waits on that work when any plugins (including Roomote file plugins) @@ -309,8 +320,9 @@ function quoteForBash(value: string): string { async function materializeOpenCodeBashEnvOverlay(options: { commandEnv: Record; homeDir: string; + harnessOnlySecretNames: string[]; }): Promise { - const { commandEnv, homeDir } = options; + const { commandEnv, homeDir, harnessOnlySecretNames } = options; const inheritedBashEnv = commandEnv.BASH_ENV?.trim(); if (!inheritedBashEnv) { @@ -320,6 +332,22 @@ async function materializeOpenCodeBashEnvOverlay(options: { const dataDir = resolveOpenCodeDataDir(homeDir, commandEnv); await fs.mkdir(dataDir, { recursive: true }); const overlayPath = path.join(dataDir, OPENCODE_BASH_ENV_FILE_NAME); + const secretUnsetLines = harnessOnlySecretNames.map( + (envVarName) => `unset ${envVarName}`, + ); + const harnessSecretOverlay = + secretUnsetLines.length === 0 + ? [] + : commandEnv.OPENCODE_COMMAND?.trim() + ? [ + 'if [[ "${ROOMOTE_OPENCODE_LAUNCH_ENV_INITIALIZED:-}" == "1" ]]; then', + ...secretUnsetLines.map((line) => ` ${line}`), + 'else', + ' export ROOMOTE_OPENCODE_LAUNCH_ENV_INITIALIZED=1', + 'fi', + ] + : secretUnsetLines; + await fs.writeFile( overlayPath, [ @@ -329,6 +357,7 @@ async function materializeOpenCodeBashEnvOverlay(options: { ...DISABLED_MODEL_PROVIDER_ENV_VAR_NAMES.map( (envVarName) => `unset ${envVarName}`, ), + ...harnessSecretOverlay, '', ].join('\n'), { mode: 0o600 }, diff --git a/apps/worker/src/sandbox-server/procedures/__tests__/reloadDeploymentEnvVars.test.ts b/apps/worker/src/sandbox-server/procedures/__tests__/reloadDeploymentEnvVars.test.ts index 53c80d0db..4a1c2d421 100644 --- a/apps/worker/src/sandbox-server/procedures/__tests__/reloadDeploymentEnvVars.test.ts +++ b/apps/worker/src/sandbox-server/procedures/__tests__/reloadDeploymentEnvVars.test.ts @@ -53,6 +53,7 @@ function createWorkerEnv() { GH_TOKEN: 'gh-token', LEGACY_VALUE: 'old-value', }); + workerEnv.setModelRuntimeEnv({ STALE_MODEL_KEY: 'revoked-secret' }); workerEnv.addUserEnv({ NEXT_PUBLIC_API_BASE: 'https://workspace.example.test', }); @@ -70,14 +71,17 @@ function createCaller(workerEnv?: WorkerEnv, runId = 1) { ROOMOTE_TASK_ID: 'task-123', ROOMOTE_TASK_TYPE: 'standard', CLAUDE_APPEND_SYSTEM_PROMPT: 'follow the system instructions', + STALE_MODEL_KEY: 'revoked-secret', }; const setCommandEnv = vi.fn(); + const requestReconnect = vi.fn().mockResolvedValue(undefined); const ctx = { workingDirectory: '/tmp', harness: { isConnected: true, getCommandEnv: () => ({ ...commandEnv }), setCommandEnv, + requestReconnect, }, harnessManager: { getStatus: () => ({ @@ -99,7 +103,11 @@ function createCaller(workerEnv?: WorkerEnv, runId = 1) { workerEnv, } as unknown as Context; - return { caller: appRouter.createCaller(ctx), setCommandEnv }; + return { + caller: appRouter.createCaller(ctx), + setCommandEnv, + requestReconnect, + }; } describe('reloadDeploymentEnvVars procedure', () => { @@ -107,8 +115,11 @@ describe('reloadDeploymentEnvVars procedure', () => { vi.clearAllMocks(); resetCredentialWriteBarrierForTesting(); mockGetResolvedRuntimeEnvVars.mockResolvedValue({ - OPENAI_API_KEY: 'new-openai-key', - ANTHROPIC_API_KEY: 'new-anthropic-key', + envVars: { MY_APP_CONFIG: 'new-app-value' }, + modelRuntimeEnv: { + OPENAI_API_KEY: 'new-openai-key', + ANTHROPIC_API_KEY: 'new-anthropic-key', + }, }); mockFindFirstById.mockResolvedValue({ id: 1, taskId: 'task-123' }); mockInjectEnvVars.mockImplementation( @@ -120,24 +131,21 @@ describe('reloadDeploymentEnvVars procedure', () => { it('replaces user env vars while preserving service env and shell wiring', async () => { const workerEnv = createWorkerEnv(); - const { caller, setCommandEnv } = createCaller(workerEnv); + const { caller, setCommandEnv, requestReconnect } = createCaller(workerEnv); const result = await caller.commands.reloadDeploymentEnvVars(); expect(result.success).toBe(true); - expect(result.names).toEqual( - expect.arrayContaining(['OPENAI_API_KEY', 'ANTHROPIC_API_KEY']), - ); - expect(result.names).toHaveLength(2); + expect(result.names).toEqual(['MY_APP_CONFIG']); expect(mockGetResolvedRuntimeEnvVars).toHaveBeenCalledWith({ runId: 1, + envContractVersion: 2, }); expect(mockFindFirstById).toHaveBeenCalledWith(1); expect(mockInjectEnvVars).toHaveBeenCalledTimes(1); expect(mockInjectEnvVars).toHaveBeenCalledWith( expect.objectContaining({ - OPENAI_API_KEY: 'new-openai-key', - ANTHROPIC_API_KEY: 'new-anthropic-key', + MY_APP_CONFIG: 'new-app-value', }), { id: 1, taskId: 'task-123' }, expect.objectContaining({ @@ -150,8 +158,18 @@ describe('reloadDeploymentEnvVars procedure', () => { const reloadedEnv = workerEnv.buildUserFacingEnv(); expect(reloadedEnv.DATABASE_URL).toBe('postgres://localhost/test'); - expect(reloadedEnv.OPENAI_API_KEY).toBe('new-openai-key'); - expect(reloadedEnv.ANTHROPIC_API_KEY).toBe('new-anthropic-key'); + expect(reloadedEnv.MY_APP_CONFIG).toBe('new-app-value'); + expect(reloadedEnv).not.toHaveProperty('OPENAI_API_KEY'); + expect(reloadedEnv).not.toHaveProperty('ANTHROPIC_API_KEY'); + expect(setCommandEnv).toHaveBeenCalledWith( + expect.objectContaining({ + OPENAI_API_KEY: 'new-openai-key', + ANTHROPIC_API_KEY: 'new-anthropic-key', + }), + ); + expect(setCommandEnv.mock.calls[0]?.[0]).not.toHaveProperty( + 'STALE_MODEL_KEY', + ); expect(reloadedEnv.GH_TOKEN).toBeUndefined(); expect(reloadedEnv.NEXT_PUBLIC_API_BASE).toBe( 'https://workspace.example.test', @@ -164,11 +182,15 @@ describe('reloadDeploymentEnvVars procedure', () => { LC_ALL: 'C.UTF-8', OPENAI_API_KEY: 'new-openai-key', ANTHROPIC_API_KEY: 'new-anthropic-key', + MY_APP_CONFIG: 'new-app-value', BASH_ENV: '/tmp/roomote/env.sh', ROOMOTE_TASK_ID: 'task-123', ROOMOTE_TASK_TYPE: 'standard', CLAUDE_APPEND_SYSTEM_PROMPT: 'follow the system instructions', }); + expect(requestReconnect).toHaveBeenCalledWith({ + reason: 'model runtime environment changed', + }); }); it('fails when the worker env is unavailable', async () => { @@ -181,6 +203,25 @@ describe('reloadDeploymentEnvVars procedure', () => { }); }); + it('keeps model credentials out of generic env for legacy flat responses', async () => { + mockGetResolvedRuntimeEnvVars.mockResolvedValue({ + MY_APP_CONFIG: 'legacy-app-value', + OPENAI_API_KEY: 'legacy-model-secret', + }); + const { caller, setCommandEnv } = createCaller(createWorkerEnv()); + + await caller.commands.reloadDeploymentEnvVars(); + + expect(mockInjectEnvVars).toHaveBeenCalledWith( + expect.not.objectContaining({ OPENAI_API_KEY: expect.anything() }), + expect.anything(), + expect.anything(), + ); + expect(setCommandEnv).toHaveBeenCalledWith( + expect.objectContaining({ OPENAI_API_KEY: 'legacy-model-secret' }), + ); + }); + it('rejects without writing env files once the credential write barrier is engaged', async () => { await engageCredentialWriteBarrier(); diff --git a/apps/worker/src/sandbox-server/procedures/reloadDeploymentEnvVars.ts b/apps/worker/src/sandbox-server/procedures/reloadDeploymentEnvVars.ts index e2ce43c8a..3c882967c 100644 --- a/apps/worker/src/sandbox-server/procedures/reloadDeploymentEnvVars.ts +++ b/apps/worker/src/sandbox-server/procedures/reloadDeploymentEnvVars.ts @@ -5,6 +5,7 @@ import { sdk } from '@roomote/sdk/client'; import { injectEnvVars } from '../../commands/utils/env-vars'; import type { WorkerEnv } from '../../env'; import { runUnlessCredentialWriteBarrier } from '../../lib'; +import { splitLegacyModelRuntimeEnv } from '../../run-task/env'; import type { Harness } from '../lib/harness'; import { publicProcedure } from '../trpc'; @@ -22,6 +23,34 @@ function omitKeys( return nextEnv; } +function envValuesEqual( + left: Record, + right: Record, +): boolean { + const leftEntries = Object.entries(left); + + return ( + leftEntries.length === Object.keys(right).length && + leftEntries.every(([name, value]) => right[name] === value) + ); +} + +function isSplitResolvedEnv(value: unknown): value is { + envVars: Record; + modelRuntimeEnv: Record; +} { + return ( + typeof value === 'object' && + value !== null && + 'envVars' in value && + typeof value.envVars === 'object' && + value.envVars !== null && + 'modelRuntimeEnv' in value && + typeof value.modelRuntimeEnv === 'object' && + value.modelRuntimeEnv !== null + ); +} + /** * Fetch the deployment's current env vars and rewrite the sandbox env * (env.sh, runtime env, harness command env) from them. Shared by the live @@ -36,8 +65,8 @@ export async function applyDeploymentEnvVarsReload(input: { }): Promise<{ names: string[]; envVars: Record }> { const { runId, workerEnv, harness } = input; - const [freshEnvVars, taskRun] = await Promise.all([ - sdk.taskRuns.getResolvedRuntimeEnvVars({ runId }), + const [resolvedEnv, taskRun] = await Promise.all([ + sdk.taskRuns.getResolvedRuntimeEnvVars({ runId, envContractVersion: 2 }), sdk.taskRuns.findFirstById(runId), ]); @@ -48,7 +77,13 @@ export async function applyDeploymentEnvVarsReload(input: { }); } + const normalizedEnv = isSplitResolvedEnv(resolvedEnv) + ? resolvedEnv + : splitLegacyModelRuntimeEnv(resolvedEnv); + const { envVars: freshEnvVars, modelRuntimeEnv } = normalizedEnv; + const currentRuntimeEnv = workerEnv.getRuntimeEnv(); + const currentModelRuntimeEnv = workerEnv.getModelRuntimeEnv(); const nextRuntimeEnv: Record = { ...freshEnvVars }; await injectEnvVars(nextRuntimeEnv, taskRun, { @@ -58,18 +93,33 @@ export async function applyDeploymentEnvVarsReload(input: { }); workerEnv.setRuntimeEnv(nextRuntimeEnv); + workerEnv.setModelRuntimeEnv(modelRuntimeEnv); const currentCommandEnv = harness.getCommandEnv?.() ?? {}; - const baseCommandEnv = omitKeys( - currentCommandEnv, - Object.keys(currentRuntimeEnv), - ); + const baseCommandEnv = omitKeys(currentCommandEnv, [ + ...Object.keys(currentRuntimeEnv), + ...Object.keys(currentModelRuntimeEnv), + ]); harness.setCommandEnv?.({ ...baseCommandEnv, ...nextRuntimeEnv, + ...modelRuntimeEnv, }); + if (!envValuesEqual(currentModelRuntimeEnv, modelRuntimeEnv)) { + if (!harness.requestReconnect) { + throw new TRPCError({ + code: 'PRECONDITION_FAILED', + message: 'Model credentials require a reconnectable harness', + }); + } + + await harness.requestReconnect({ + reason: 'model runtime environment changed', + }); + } + return { names: Object.keys(freshEnvVars).sort((left, right) => left.localeCompare(right), diff --git a/packages/db/drizzle/0028_odd_wolfsbane.sql b/packages/db/drizzle/0028_odd_wolfsbane.sql new file mode 100644 index 000000000..633917274 --- /dev/null +++ b/packages/db/drizzle/0028_odd_wolfsbane.sql @@ -0,0 +1,71 @@ +CREATE TABLE "model_provider_environment_variables" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "name" text NOT NULL, + "value" text NOT NULL, + "created_by_user_id" text, + "last_updated_by_user_id" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "model_provider_environment_variables" ADD CONSTRAINT "model_provider_environment_variables_created_by_user_id_users_id_fk" FOREIGN KEY ("created_by_user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "model_provider_environment_variables" ADD CONSTRAINT "model_provider_environment_variables_last_updated_by_user_id_users_id_fk" FOREIGN KEY ("last_updated_by_user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "model_provider_environment_variables_name_unique" ON "model_provider_environment_variables" USING btree ("name");--> statement-breakpoint +-- Keep the legacy rows for N-1 rollback. Model-provider values are dual-written +-- until the previous application release is no longer a supported rollback. +INSERT INTO "model_provider_environment_variables" ( + "name", + "value", + "created_by_user_id", + "last_updated_by_user_id", + "created_at", + "updated_at" +) +SELECT + "name", + "value", + "created_by_user_id", + "last_updated_by_user_id", + "created_at", + "updated_at" +FROM "environment_variables" +WHERE "name" IN ( + 'OPENROUTER_API_KEY', + 'AI_GATEWAY_API_KEY', + 'REQUESTY_API_KEY', + 'BASETEN_API_KEY', + 'TOGETHER_API_KEY', + 'OPENAI_API_KEY', + 'AZURE_API_KEY', + 'AZURE_RESOURCE_NAME', + 'AZURE_COGNITIVE_SERVICES_API_KEY', + 'AZURE_COGNITIVE_SERVICES_RESOURCE_NAME', + 'ANTHROPIC_API_KEY', + 'MOONSHOT_API_KEY', + 'KIMI_API_KEY', + 'MINIMAX_API_KEY', + 'OPENCODE_API_KEY', + 'AWS_BEARER_TOKEN_BEDROCK', + 'AWS_REGION', + 'GEMINI_API_KEY', + 'GOOGLE_GENERATIVE_AI_API_KEY', + 'XAI_API_KEY', + 'ZAI_API_KEY', + 'ZAI_REGION', + 'ZAI_CODING_PLAN_API_KEY', + 'ZAI_CODING_PLAN_REGION', + 'OPENAI_COMPATIBLE_BASE_URL', + 'OPENAI_COMPATIBLE_API_KEY', + 'LITELLM_BASE_URL', + 'LITELLM_API_KEY', + 'OLLAMA_BASE_URL', + 'VLLM_BASE_URL', + 'VLLM_API_KEY', + 'R_MODEL_ENV_KEYS' +) +OR ( + "name" LIKE 'OPENAI_COMPATIBLE_%_BASE_URL' + OR "name" LIKE 'OPENAI_COMPATIBLE_%_API_KEY' + OR "name" LIKE 'OPENAI_COMPATIBLE_%_LABEL' +) +ON CONFLICT ("name") DO NOTHING; diff --git a/packages/db/drizzle/meta/0028_snapshot.json b/packages/db/drizzle/meta/0028_snapshot.json new file mode 100644 index 000000000..f4f709a34 --- /dev/null +++ b/packages/db/drizzle/meta/0028_snapshot.json @@ -0,0 +1,10020 @@ +{ + "id": "83998a9c-395e-465e-aedd-b159b4682893", + "prevId": "7ac28ef9-b3cd-4628-9b9e-b7ed6d608c2a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.auth_accounts": { + "name": "auth_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_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 + }, + "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 + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "auth_accounts_user_id_idx": { + "name": "auth_accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_accounts_provider_account_unique": { + "name": "auth_accounts_provider_account_unique", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_accounts_user_id_auth_users_id_fk": { + "name": "auth_accounts_user_id_auth_users_id_fk", + "tableFrom": "auth_accounts", + "tableTo": "auth_users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_sessions": { + "name": "auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "auth_sessions_token_unique": { + "name": "auth_sessions_token_unique", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_sessions_user_id_idx": { + "name": "auth_sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_sessions_user_id_auth_users_id_fk": { + "name": "auth_sessions_user_id_auth_users_id_fk", + "tableFrom": "auth_sessions", + "tableTo": "auth_users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_users": { + "name": "auth_users", + "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 + }, + "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, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "auth_users_email_unique": { + "name": "auth_users_email_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_users_created_at_idx": { + "name": "auth_users_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.auth_verifications": { + "name": "auth_verifications", + "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": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "auth_verifications_identifier_idx": { + "name": "auth_verifications_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automations": { + "name": "automations", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "internal": { + "name": "internal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "schedule": { + "name": "schedule", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "targets": { + "name": "targets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_succeeded_at": { + "name": "last_succeeded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scan_cursor": { + "name": "scan_cursor", + "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()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compute_provider_usage": { + "name": "compute_provider_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_usage_id": { + "name": "provider_usage_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "launch_mode": { + "name": "launch_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_action": { + "name": "lifecycle_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "measurement_source": { + "name": "measurement_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configured_vcpus": { + "name": "configured_vcpus", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "configured_cpu_cores": { + "name": "configured_cpu_cores", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "configured_memory_mib": { + "name": "configured_memory_mib", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "wall_clock_duration_ms": { + "name": "wall_clock_duration_ms", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "active_cpu_duration_ms": { + "name": "active_cpu_duration_ms", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "observed_memory_mib_milliseconds": { + "name": "observed_memory_mib_milliseconds", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "network_ingress_bytes": { + "name": "network_ingress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "network_egress_bytes": { + "name": "network_egress_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "compute_provider_usage_provider_usage_id_unique": { + "name": "compute_provider_usage_provider_usage_id_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_usage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "compute_provider_usage_run_id_idx": { + "name": "compute_provider_usage_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "compute_provider_usage_task_id_idx": { + "name": "compute_provider_usage_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "compute_provider_usage_created_at_idx": { + "name": "compute_provider_usage_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "compute_provider_usage_run_id_task_runs_id_fk": { + "name": "compute_provider_usage_run_id_task_runs_id_fk", + "tableFrom": "compute_provider_usage", + "tableTo": "task_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "compute_provider_usage_task_id_tasks_id_fk": { + "name": "compute_provider_usage_task_id_tasks_id_fk", + "tableFrom": "compute_provider_usage", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.compute_provider_usage_samples": { + "name": "compute_provider_usage_samples", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_usage_id": { + "name": "provider_usage_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sampled_at": { + "name": "sampled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "cpu_usage_ns_total": { + "name": "cpu_usage_ns_total", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "memory_usage_bytes": { + "name": "memory_usage_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "memory_peak_usage_bytes": { + "name": "memory_peak_usage_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "compute_provider_usage_samples_provider_usage_sampled_at_unique": { + "name": "compute_provider_usage_samples_provider_usage_sampled_at_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_usage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sampled_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "compute_provider_usage_samples_run_id_idx": { + "name": "compute_provider_usage_samples_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "compute_provider_usage_samples_task_id_idx": { + "name": "compute_provider_usage_samples_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "compute_provider_usage_samples_created_at_idx": { + "name": "compute_provider_usage_samples_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "compute_provider_usage_samples_run_id_task_runs_id_fk": { + "name": "compute_provider_usage_samples_run_id_task_runs_id_fk", + "tableFrom": "compute_provider_usage_samples", + "tableTo": "task_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "compute_provider_usage_samples_task_id_tasks_id_fk": { + "name": "compute_provider_usage_samples_task_id_tasks_id_fk", + "tableFrom": "compute_provider_usage_samples", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_automations": { + "name": "custom_automations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "schedule_mode": { + "name": "schedule_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'off'" + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_succeeded_at": { + "name": "last_succeeded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_launched_task_id": { + "name": "last_launched_task_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "launch_claimed_at": { + "name": "launch_claimed_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": { + "custom_automations_name_unique_idx": { + "name": "custom_automations_name_unique_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_automations_enabled_idx": { + "name": "custom_automations_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_automations_environment_id_idx": { + "name": "custom_automations_environment_id_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_automations_environment_id_environments_id_fk": { + "name": "custom_automations_environment_id_environments_id_fk", + "tableFrom": "custom_automations", + "tableTo": "environments", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "custom_automations_created_by_user_id_users_id_fk": { + "name": "custom_automations_created_by_user_id_users_id_fk", + "tableFrom": "custom_automations", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "custom_automations_last_launched_task_id_tasks_id_fk": { + "name": "custom_automations_last_launched_task_id_tasks_id_fk", + "tableFrom": "custom_automations", + "tableTo": "tasks", + "columnsFrom": ["last_launched_task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_mcp_enablements": { + "name": "deployment_mcp_enablements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "mcp_id": { + "name": "mcp_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enabled_by_user_id": { + "name": "enabled_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled_tools": { + "name": "disabled_tools", + "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": {}, + "foreignKeys": { + "deployment_mcp_enablements_enabled_by_user_id_users_id_fk": { + "name": "deployment_mcp_enablements_enabled_by_user_id_users_id_fk", + "tableFrom": "deployment_mcp_enablements", + "tableTo": "users", + "columnsFrom": ["enabled_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "deployment_mcp_enablements_mcp_unique": { + "name": "deployment_mcp_enablements_mcp_unique", + "nullsNotDistinct": false, + "columns": ["mcp_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_secrets": { + "name": "deployment_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "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": { + "deployment_secrets_name_unique": { + "name": "deployment_secrets_name_unique", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_settings": { + "name": "deployment_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "default": "'default'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "task_model_settings": { + "name": "task_model_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "router_debug_provider": { + "name": "router_debug_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "router_debug_channel_id": { + "name": "router_debug_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "router_debug_disabled": { + "name": "router_debug_disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "router_debug_slack_channel_id": { + "name": "router_debug_slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_model_config": { + "name": "runtime_model_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runtime_compute_config": { + "name": "runtime_compute_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "access_policy": { + "name": "access_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "license_key": { + "name": "license_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instance_analytics_id": { + "name": "instance_analytics_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_known_version": { + "name": "latest_known_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_version_checked_at": { + "name": "latest_version_checked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "setup_completed_at": { + "name": "setup_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "setup_new_state": { + "name": "setup_new_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "slack_onboarding_stage": { + "name": "slack_onboarding_stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manager_slack_channel_id": { + "name": "manager_slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manager_discord_channel_id": { + "name": "manager_discord_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "global_agent_instructions": { + "name": "global_agent_instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_zone": { + "name": "time_zone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_zone_updated_at": { + "name": "time_zone_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "authorship_instructions": { + "name": "authorship_instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "compiled_authorship_rules": { + "name": "compiled_authorship_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "compiled_authorship_issues": { + "name": "compiled_authorship_issues", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "compiled_authorship_at": { + "name": "compiled_authorship_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "style_guidance": { + "name": "style_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_summon_emoji": { + "name": "slack_summon_emoji", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_ack_emoji": { + "name": "slack_ack_emoji", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'eyes'" + }, + "slack_completion_emoji": { + "name": "slack_completion_emoji", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'white_check_mark'" + }, + "created_at": { + "name": "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.discord_gateway_sessions": { + "name": "discord_gateway_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resume_gateway_url": { + "name": "resume_gateway_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sequence": { + "name": "sequence", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "shard_count": { + "name": "shard_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_connected_at": { + "name": "last_connected_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_heartbeat_ack_at": { + "name": "last_heartbeat_ack_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "disconnected_at": { + "name": "disconnected_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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord_installation_channels": { + "name": "discord_installation_channels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "discord_installation_id": { + "name": "discord_installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_name": { + "name": "channel_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel_type": { + "name": "channel_type", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_available": { + "name": "is_available", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "discord_installation_channels_installation_id_idx": { + "name": "discord_installation_channels_installation_id_idx", + "columns": [ + { + "expression": "discord_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "discord_installation_channels_unique": { + "name": "discord_installation_channels_unique", + "columns": [ + { + "expression": "discord_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "discord_installation_channels_discord_installation_id_discord_installations_id_fk": { + "name": "discord_installation_channels_discord_installation_id_discord_installations_id_fk", + "tableFrom": "discord_installation_channels", + "tableTo": "discord_installations", + "columnsFrom": ["discord_installation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord_installations": { + "name": "discord_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "guild_id": { + "name": "guild_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "guild_name": { + "name": "guild_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "installed_by_user_id": { + "name": "installed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_channel_id": { + "name": "default_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_channel_name": { + "name": "default_channel_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_channel_type": { + "name": "default_channel_type", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "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": { + "discord_installations_guild_id_unique": { + "name": "discord_installations_guild_id_unique", + "columns": [ + { + "expression": "guild_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "discord_installations_active_idx": { + "name": "discord_installations_active_idx", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "discord_installations_default_channel_idx": { + "name": "discord_installations_default_channel_idx", + "columns": [ + { + "expression": "default_channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "discord_installations_installed_by_user_id_users_id_fk": { + "name": "discord_installations_installed_by_user_id_users_id_fk", + "tableFrom": "discord_installations", + "tableTo": "users", + "columnsFrom": ["installed_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord_user_mappings": { + "name": "discord_user_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "discord_user_id": { + "name": "discord_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "discord_username": { + "name": "discord_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discord_global_name": { + "name": "discord_global_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discord_dm_channel_id": { + "name": "discord_dm_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_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": { + "discord_user_mappings_user_id_idx": { + "name": "discord_user_mappings_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "discord_user_mappings_discord_user_id_unique": { + "name": "discord_user_mappings_discord_user_id_unique", + "columns": [ + { + "expression": "discord_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "discord_user_mappings_user_id_users_id_fk": { + "name": "discord_user_mappings_user_id_users_id_fk", + "tableFrom": "discord_user_mappings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_config_versions": { + "name": "environment_config_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_config_versions_environment_id_idx": { + "name": "environment_config_versions_environment_id_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_config_versions_environment_version_unique": { + "name": "environment_config_versions_environment_version_unique", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_config_versions_environment_id_environments_id_fk": { + "name": "environment_config_versions_environment_id_environments_id_fk", + "tableFrom": "environment_config_versions", + "tableTo": "environments", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_config_versions_created_by_user_id_users_id_fk": { + "name": "environment_config_versions_created_by_user_id_users_id_fk", + "tableFrom": "environment_config_versions", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_repository_mappings": { + "name": "environment_repository_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "env_repo_mappings_env_id_idx": { + "name": "env_repo_mappings_env_id_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "env_repo_mappings_repo_id_idx": { + "name": "env_repo_mappings_repo_id_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_repository_mappings_environment_id_environments_id_fk": { + "name": "environment_repository_mappings_environment_id_environments_id_fk", + "tableFrom": "environment_repository_mappings", + "tableTo": "environments", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_repository_mappings_repository_id_repositories_id_fk": { + "name": "environment_repository_mappings_repository_id_repositories_id_fk", + "tableFrom": "environment_repository_mappings", + "tableTo": "repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "env_repo_mappings_unique": { + "name": "env_repo_mappings_unique", + "nullsNotDistinct": false, + "columns": ["environment_id", "repository_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_snapshots": { + "name": "environment_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot_created_at": { + "name": "snapshot_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "snapshot_expires_at": { + "name": "snapshot_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "snapshot_status": { + "name": "snapshot_status", + "type": "text", + "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": { + "environment_snapshots_environment_id_idx": { + "name": "environment_snapshots_environment_id_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_snapshots_env_provider_unique": { + "name": "environment_snapshots_env_provider_unique", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_snapshots\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_snapshots_environment_id_environments_id_fk": { + "name": "environment_snapshots_environment_id_environments_id_fk", + "tableFrom": "environment_snapshots", + "tableTo": "environments", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_variables": { + "name": "environment_variables", + "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": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_updated_by_user_id": { + "name": "last_updated_by_user_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": { + "environment_variables_user_id_idx": { + "name": "environment_variables_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_variables_name_unique": { + "name": "environment_variables_name_unique", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_variables_user_id_users_id_fk": { + "name": "environment_variables_user_id_users_id_fk", + "tableFrom": "environment_variables", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_variables_created_by_user_id_users_id_fk": { + "name": "environment_variables_created_by_user_id_users_id_fk", + "tableFrom": "environment_variables", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "environment_variables_last_updated_by_user_id_users_id_fk": { + "name": "environment_variables_last_updated_by_user_id_users_id_fk", + "tableFrom": "environment_variables", + "tableTo": "users", + "columnsFrom": ["last_updated_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environments": { + "name": "environments", + "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": false + }, + "created_by_user_id": { + "name": "created_by_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": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "is_eval": { + "name": "is_eval", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "declarative_source": { + "name": "declarative_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_verified": { + "name": "is_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verification_task_id": { + "name": "verification_task_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "verification_error": { + "name": "verification_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot_created_at": { + "name": "snapshot_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "snapshot_expires_at": { + "name": "snapshot_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "snapshot_status": { + "name": "snapshot_status", + "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": { + "environments_user_id_idx": { + "name": "environments_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_created_by_user_id_idx": { + "name": "environments_created_by_user_id_idx", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_snapshot_expires_at_idx": { + "name": "environments_snapshot_expires_at_idx", + "columns": [ + { + "expression": "snapshot_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_name_unique": { + "name": "environments_name_unique", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environments_user_id_users_id_fk": { + "name": "environments_user_id_users_id_fk", + "tableFrom": "environments", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environments_created_by_user_id_users_id_fk": { + "name": "environments_created_by_user_id_users_id_fk", + "tableFrom": "environments", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_installations": { + "name": "github_installations", + "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": false + }, + "installation_id": { + "name": "installation_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "account_login": { + "name": "account_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_type": { + "name": "account_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "installed_by_user_id": { + "name": "installed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "members_count": { + "name": "members_count", + "type": "integer", + "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": { + "github_installations_account_login_idx": { + "name": "github_installations_account_login_idx", + "columns": [ + { + "expression": "account_login", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_installations_deployment_installation_unique": { + "name": "github_installations_deployment_installation_unique", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_installations_user_id_users_id_fk": { + "name": "github_installations_user_id_users_id_fk", + "tableFrom": "github_installations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_installations_installed_by_user_id_users_id_fk": { + "name": "github_installations_installed_by_user_id_users_id_fk", + "tableFrom": "github_installations", + "tableTo": "users", + "columnsFrom": ["installed_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_pending_installations": { + "name": "github_pending_installations", + "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": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "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": { + "github_pending_installations_requested_by_user_id_idx": { + "name": "github_pending_installations_requested_by_user_id_idx", + "columns": [ + { + "expression": "requested_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_pending_installations_user_id_users_id_fk": { + "name": "github_pending_installations_user_id_users_id_fk", + "tableFrom": "github_pending_installations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_pending_installations_requested_by_user_id_users_id_fk": { + "name": "github_pending_installations_requested_by_user_id_users_id_fk", + "tableFrom": "github_pending_installations", + "tableTo": "users", + "columnsFrom": ["requested_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_user_mappings": { + "name": "github_user_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_user_id": { + "name": "github_user_id", + "type": "bigint", + "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 + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_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": { + "github_user_mappings_github_login_idx": { + "name": "github_user_mappings_github_login_idx", + "columns": [ + { + "expression": "github_login", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_user_mappings_user_id_idx": { + "name": "github_user_mappings_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_user_mappings_user_id_users_id_fk": { + "name": "github_user_mappings_user_id_users_id_fk", + "tableFrom": "github_user_mappings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_user_mappings_unique": { + "name": "github_user_mappings_unique", + "nullsNotDistinct": false, + "columns": ["github_user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "max_uses": { + "name": "max_uses", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "used_count": { + "name": "used_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_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": { + "invites_token_hash_unique": { + "name": "invites_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_created_at_idx": { + "name": "invites_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_invited_by_user_id_users_id_fk": { + "name": "invites_invited_by_user_id_users_id_fk", + "tableFrom": "invites", + "tableTo": "users", + "columnsFrom": ["invited_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.linear_pending_selections": { + "name": "linear_pending_selections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "linear_organization_id": { + "name": "linear_organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "step": { + "name": "step", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'awaiting_workspace'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "selected_repo": { + "name": "selected_repo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_options": { + "name": "workspace_options", + "type": "jsonb", + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "linear_pending_selections_expires_at_idx": { + "name": "linear_pending_selections_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "linear_pending_selections_step_idx": { + "name": "linear_pending_selections_step_idx", + "columns": [ + { + "expression": "step", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "linear_pending_selections_user_id_users_id_fk": { + "name": "linear_pending_selections_user_id_users_id_fk", + "tableFrom": "linear_pending_selections", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "linear_pending_selections_session_id_unique": { + "name": "linear_pending_selections_session_id_unique", + "nullsNotDistinct": false, + "columns": ["session_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_inference_usage_events": { + "name": "task_inference_usage_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'opencode'" + }, + "usage_type": { + "name": "usage_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inference'" + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "harness_session_id": { + "name": "harness_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reasoning_tokens": { + "name": "reasoning_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cache_read_tokens": { + "name": "cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens": { + "name": "total_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "context_tokens": { + "name": "context_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_micro_usd": { + "name": "cost_micro_usd", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_source": { + "name": "cost_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pricing_metadata": { + "name": "pricing_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "message_created_at": { + "name": "message_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "message_completed_at": { + "name": "message_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "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": { + "task_inference_usage_events_session_message_unique": { + "name": "task_inference_usage_events_session_message_unique", + "columns": [ + { + "expression": "harness_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_inference_usage_events_event_key_unique": { + "name": "task_inference_usage_events_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_inference_usage_events_task_id_idx": { + "name": "task_inference_usage_events_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_inference_usage_events_run_id_idx": { + "name": "task_inference_usage_events_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_inference_usage_events_user_id_idx": { + "name": "task_inference_usage_events_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_inference_usage_events_environment_id_idx": { + "name": "task_inference_usage_events_environment_id_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_inference_usage_events_provider_model_idx": { + "name": "task_inference_usage_events_provider_model_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_inference_usage_events_created_at_idx": { + "name": "task_inference_usage_events_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_inference_usage_events_task_id_tasks_id_fk": { + "name": "task_inference_usage_events_task_id_tasks_id_fk", + "tableFrom": "task_inference_usage_events", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_inference_usage_events_run_id_task_runs_id_fk": { + "name": "task_inference_usage_events_run_id_task_runs_id_fk", + "tableFrom": "task_inference_usage_events", + "tableTo": "task_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "task_inference_usage_events_user_id_users_id_fk": { + "name": "task_inference_usage_events_user_id_users_id_fk", + "tableFrom": "task_inference_usage_events", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "task_inference_usage_events_environment_id_environments_id_fk": { + "name": "task_inference_usage_events_environment_id_environments_id_fk", + "tableFrom": "task_inference_usage_events", + "tableTo": "environments", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_connections": { + "name": "mcp_connections", + "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": false + }, + "mcp_id": { + "name": "mcp_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_role": { + "name": "connection_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "auth_config": { + "name": "auth_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auth_status": { + "name": "auth_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "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()" + } + }, + "indexes": { + "mcp_connections_user_id_idx": { + "name": "mcp_connections_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_connections_role_idx": { + "name": "mcp_connections_role_idx", + "columns": [ + { + "expression": "mcp_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_connections_user_id_users_id_fk": { + "name": "mcp_connections_user_id_users_id_fk", + "tableFrom": "mcp_connections", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mcp_connections_user_mcp_id_unique": { + "name": "mcp_connections_user_mcp_id_unique", + "nullsNotDistinct": true, + "columns": ["user_id", "mcp_id", "connection_role"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_replays": { + "name": "mcp_oauth_replays", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_id": { + "name": "mcp_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_role": { + "name": "connection_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "redirect_to": { + "name": "redirect_to", + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_oauth_replays_connection_id_idx": { + "name": "mcp_oauth_replays_connection_id_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_replays_user_id_idx": { + "name": "mcp_oauth_replays_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_oauth_replays_expires_at_idx": { + "name": "mcp_oauth_replays_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_oauth_replays_connection_id_mcp_connections_id_fk": { + "name": "mcp_oauth_replays_connection_id_mcp_connections_id_fk", + "tableFrom": "mcp_oauth_replays", + "tableTo": "mcp_connections", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_oauth_replays_user_id_users_id_fk": { + "name": "mcp_oauth_replays_user_id_users_id_fk", + "tableFrom": "mcp_oauth_replays", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mcp_oauth_replays_token_unique": { + "name": "mcp_oauth_replays_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.microsoft_auth_user_mappings": { + "name": "microsoft_auth_user_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "auth_account_id": { + "name": "auth_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "microsoft_tenant_id": { + "name": "microsoft_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "microsoft_aad_object_id": { + "name": "microsoft_aad_object_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": { + "microsoft_auth_user_mappings_user_id_idx": { + "name": "microsoft_auth_user_mappings_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "microsoft_auth_user_mappings_account_id_idx": { + "name": "microsoft_auth_user_mappings_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "microsoft_auth_user_mappings_auth_account_idx": { + "name": "microsoft_auth_user_mappings_auth_account_idx", + "columns": [ + { + "expression": "auth_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "microsoft_auth_user_mappings_aad_object_unique": { + "name": "microsoft_auth_user_mappings_aad_object_unique", + "columns": [ + { + "expression": "microsoft_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "microsoft_aad_object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "microsoft_auth_user_mappings_auth_account_id_auth_accounts_id_fk": { + "name": "microsoft_auth_user_mappings_auth_account_id_auth_accounts_id_fk", + "tableFrom": "microsoft_auth_user_mappings", + "tableTo": "auth_accounts", + "columnsFrom": ["auth_account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "microsoft_auth_user_mappings_user_id_auth_users_id_fk": { + "name": "microsoft_auth_user_mappings_user_id_auth_users_id_fk", + "tableFrom": "microsoft_auth_user_mappings", + "tableTo": "auth_users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_provider_environment_variables": { + "name": "model_provider_environment_variables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_updated_by_user_id": { + "name": "last_updated_by_user_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": { + "model_provider_environment_variables_name_unique": { + "name": "model_provider_environment_variables_name_unique", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_provider_environment_variables_created_by_user_id_users_id_fk": { + "name": "model_provider_environment_variables_created_by_user_id_users_id_fk", + "tableFrom": "model_provider_environment_variables", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "model_provider_environment_variables_last_updated_by_user_id_users_id_fk": { + "name": "model_provider_environment_variables_last_updated_by_user_id_users_id_fk", + "tableFrom": "model_provider_environment_variables", + "tableTo": "users", + "columnsFrom": ["last_updated_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_state": { + "name": "oauth_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replay_token": { + "name": "replay_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oauth_state_connection_id_idx": { + "name": "oauth_state_connection_id_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_state_replay_token_idx": { + "name": "oauth_state_replay_token_idx", + "columns": [ + { + "expression": "replay_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_state_expires_at_idx": { + "name": "oauth_state_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_state_connection_id_mcp_connections_id_fk": { + "name": "oauth_state_connection_id_mcp_connections_id_fk", + "tableFrom": "oauth_state", + "tableTo": "mcp_connections", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pull_request_facts": { + "name": "pull_request_facts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repository_full_name": { + "name": "repository_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_control_provider": { + "name": "source_control_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "external_pull_request_id": { + "name": "external_pull_request_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "html_url": { + "name": "html_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_login": { + "name": "author_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at_remote": { + "name": "created_at_remote", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at_remote": { + "name": "updated_at_remote", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "closed_at_remote": { + "name": "closed_at_remote", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "merged_at_remote": { + "name": "merged_at_remote", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pull_request_facts_deployment_repo_pr_unique": { + "name": "pull_request_facts_deployment_repo_pr_unique", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pull_request_facts_deployment_created_idx": { + "name": "pull_request_facts_deployment_created_idx", + "columns": [ + { + "expression": "created_at_remote", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pull_request_facts_deployment_repo_created_idx": { + "name": "pull_request_facts_deployment_repo_created_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_remote", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pull_request_facts_deployment_state_created_idx": { + "name": "pull_request_facts_deployment_state_created_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_remote", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pull_request_facts_deployment_author_created_idx": { + "name": "pull_request_facts_deployment_author_created_idx", + "columns": [ + { + "expression": "author_login", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at_remote", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pull_request_facts_deployment_updated_idx": { + "name": "pull_request_facts_deployment_updated_idx", + "columns": [ + { + "expression": "updated_at_remote", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pull_request_facts_repository_id_repositories_id_fk": { + "name": "pull_request_facts_repository_id_repositories_id_fk", + "tableFrom": "pull_request_facts", + "tableTo": "repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pull_request_facts_source_control_provider_check": { + "name": "pull_request_facts_source_control_provider_check", + "value": "\"pull_request_facts\".\"source_control_provider\" in ('github', 'gitlab', 'gitea', 'ado', 'bitbucket')" + } + }, + "isRLSEnabled": false + }, + "public.pull_request_sync_states": { + "name": "pull_request_sync_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "last_incremental_updated_at": { + "name": "last_incremental_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "backfill_completed_at": { + "name": "backfill_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cooldown_until": { + "name": "cooldown_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_successful_sync_at": { + "name": "last_successful_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_attempted_sync_at": { + "name": "last_attempted_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "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": { + "pull_request_sync_states_repo_unique": { + "name": "pull_request_sync_states_repo_unique", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pull_request_sync_states_deployment_updated_idx": { + "name": "pull_request_sync_states_deployment_updated_idx", + "columns": [ + { + "expression": "last_successful_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pull_request_sync_states_cooldown_idx": { + "name": "pull_request_sync_states_cooldown_idx", + "columns": [ + { + "expression": "cooldown_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pull_request_sync_states_repository_id_repositories_id_fk": { + "name": "pull_request_sync_states_repository_id_repositories_id_fk", + "tableFrom": "pull_request_sync_states", + "tableTo": "repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.repositories": { + "name": "repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source_control_provider": { + "name": "source_control_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_repo_id": { + "name": "github_repo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "external_repo_id": { + "name": "external_repo_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "private": { + "name": "private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "clone_url": { + "name": "clone_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "html_url": { + "name": "html_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "linked_by_user_id": { + "name": "linked_by_user_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": { + "repositories_source_control_provider_idx": { + "name": "repositories_source_control_provider_idx", + "columns": [ + { + "expression": "source_control_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "repositories_installation_id_idx": { + "name": "repositories_installation_id_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "repositories_full_name_idx": { + "name": "repositories_full_name_idx", + "columns": [ + { + "expression": "full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "repositories_provider_host_full_name_idx": { + "name": "repositories_provider_host_full_name_idx", + "columns": [ + { + "expression": "source_control_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "repositories_deployment_active_installation_idx": { + "name": "repositories_deployment_active_installation_idx", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "repositories_deployment_github_repo_unique": { + "name": "repositories_deployment_github_repo_unique", + "columns": [ + { + "expression": "github_repo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "repositories_provider_host_external_repo_unique": { + "name": "repositories_provider_host_external_repo_unique", + "columns": [ + { + "expression": "source_control_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"host\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "external_repo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "repositories_provider_host_full_name_unique": { + "name": "repositories_provider_host_full_name_unique", + "columns": [ + { + "expression": "source_control_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"host\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "repositories_installation_id_github_installations_id_fk": { + "name": "repositories_installation_id_github_installations_id_fk", + "tableFrom": "repositories", + "tableTo": "github_installations", + "columnsFrom": ["installation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "repositories_user_id_users_id_fk": { + "name": "repositories_user_id_users_id_fk", + "tableFrom": "repositories", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "repositories_linked_by_user_id_users_id_fk": { + "name": "repositories_linked_by_user_id_users_id_fk", + "tableFrom": "repositories", + "tableTo": "users", + "columnsFrom": ["linked_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "repositories_source_control_provider_check": { + "name": "repositories_source_control_provider_check", + "value": "\"repositories\".\"source_control_provider\" in ('github', 'gitlab', 'gitea', 'ado', 'bitbucket')" + }, + "repositories_github_shape_check": { + "name": "repositories_github_shape_check", + "value": "\"repositories\".\"source_control_provider\" != 'github' OR (\"repositories\".\"installation_id\" IS NOT NULL AND \"repositories\".\"github_repo_id\" IS NOT NULL)" + }, + "repositories_gitlab_shape_check": { + "name": "repositories_gitlab_shape_check", + "value": "\"repositories\".\"source_control_provider\" != 'gitlab' OR \"repositories\".\"external_repo_id\" IS NOT NULL" + }, + "repositories_gitea_shape_check": { + "name": "repositories_gitea_shape_check", + "value": "\"repositories\".\"source_control_provider\" != 'gitea' OR \"repositories\".\"external_repo_id\" IS NOT NULL" + }, + "repositories_ado_shape_check": { + "name": "repositories_ado_shape_check", + "value": "\"repositories\".\"source_control_provider\" != 'ado' OR \"repositories\".\"external_repo_id\" IS NOT NULL" + }, + "repositories_bitbucket_shape_check": { + "name": "repositories_bitbucket_shape_check", + "value": "\"repositories\".\"source_control_provider\" != 'bitbucket' OR \"repositories\".\"external_repo_id\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.sandbox_oidc_targets": { + "name": "sandbox_oidc_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "compute_provider": { + "name": "compute_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "compute_provider_id": { + "name": "compute_provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "audience": { + "name": "audience", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_file": { + "name": "token_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aws_role_arn": { + "name": "aws_role_arn", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aws_region": { + "name": "aws_region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_at": { + "name": "refresh_at", + "type": "timestamp", + "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": { + "sandbox_oidc_targets_environment_id_idx": { + "name": "sandbox_oidc_targets_environment_id_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sandbox_oidc_targets_run_id_idx": { + "name": "sandbox_oidc_targets_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sandbox_oidc_targets_refresh_at_idx": { + "name": "sandbox_oidc_targets_refresh_at_idx", + "columns": [ + { + "expression": "refresh_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sandbox_oidc_targets_provider_target_file_unique": { + "name": "sandbox_oidc_targets_provider_target_file_unique", + "columns": [ + { + "expression": "compute_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "compute_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "token_file", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sandbox_oidc_targets_environment_id_environments_id_fk": { + "name": "sandbox_oidc_targets_environment_id_environments_id_fk", + "tableFrom": "sandbox_oidc_targets", + "tableTo": "environments", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sandbox_oidc_targets_run_id_task_runs_id_fk": { + "name": "sandbox_oidc_targets_run_id_task_runs_id_fk", + "tableFrom": "sandbox_oidc_targets", + "tableTo": "task_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "sandbox_oidc_targets_owner_required": { + "name": "sandbox_oidc_targets_owner_required", + "value": "run_id IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.setup_qualification_blocks": { + "name": "setup_qualification_blocks", + "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 + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'blocked'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_domain": { + "name": "email_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_account_login": { + "name": "github_account_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_account_type": { + "name": "github_account_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_blocked_at": { + "name": "first_blocked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_blocked_at": { + "name": "last_blocked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "lifted_by_admin_user_id": { + "name": "lifted_by_admin_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifted_by_admin_email": { + "name": "lifted_by_admin_email", + "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": { + "setup_qualification_blocks_deployment_user_reason_unique": { + "name": "setup_qualification_blocks_deployment_user_reason_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reason", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "setup_qualification_blocks_deployment_status_idx": { + "name": "setup_qualification_blocks_deployment_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "setup_qualification_blocks_user_status_idx": { + "name": "setup_qualification_blocks_user_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "setup_qualification_blocks_user_id_users_id_fk": { + "name": "setup_qualification_blocks_user_id_users_id_fk", + "tableFrom": "setup_qualification_blocks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_auth_tokens": { + "name": "slack_auth_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_ts": { + "name": "thread_ts", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_text": { + "name": "original_text", + "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()" + } + }, + "indexes": { + "slack_auth_tokens_expires_at_idx": { + "name": "slack_auth_tokens_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "slack_auth_tokens_token_unique": { + "name": "slack_auth_tokens_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_conversation_messages": { + "name": "slack_conversation_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_slack_user_id": { + "name": "subject_slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sender_user_id": { + "name": "sender_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_slack_user_id": { + "name": "sender_slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_kind": { + "name": "conversation_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_ts": { + "name": "thread_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message_ts": { + "name": "message_ts", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_at": { + "name": "message_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_kind": { + "name": "author_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "slack_quick_answer_id": { + "name": "slack_quick_answer_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slack_conversation_messages_deployment_user_message_at_idx": { + "name": "slack_conversation_messages_deployment_user_message_at_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_conversation_messages_deployment_user_thread_idx": { + "name": "slack_conversation_messages_deployment_user_thread_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slack_channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_ts", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_conversation_messages_task_id_idx": { + "name": "slack_conversation_messages_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_conversation_messages_run_id_idx": { + "name": "slack_conversation_messages_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_conversation_messages_team_channel_message_unique": { + "name": "slack_conversation_messages_team_channel_message_unique", + "columns": [ + { + "expression": "slack_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slack_channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_ts", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_conversation_messages_subject_user_id_users_id_fk": { + "name": "slack_conversation_messages_subject_user_id_users_id_fk", + "tableFrom": "slack_conversation_messages", + "tableTo": "users", + "columnsFrom": ["subject_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_conversation_messages_sender_user_id_users_id_fk": { + "name": "slack_conversation_messages_sender_user_id_users_id_fk", + "tableFrom": "slack_conversation_messages", + "tableTo": "users", + "columnsFrom": ["sender_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "slack_conversation_messages_task_id_tasks_id_fk": { + "name": "slack_conversation_messages_task_id_tasks_id_fk", + "tableFrom": "slack_conversation_messages", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "slack_conversation_messages_run_id_task_runs_id_fk": { + "name": "slack_conversation_messages_run_id_task_runs_id_fk", + "tableFrom": "slack_conversation_messages", + "tableTo": "task_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "slack_conversation_messages_slack_quick_answer_id_slack_quick_answers_id_fk": { + "name": "slack_conversation_messages_slack_quick_answer_id_slack_quick_answers_id_fk", + "tableFrom": "slack_conversation_messages", + "tableTo": "slack_quick_answers", + "columnsFrom": ["slack_quick_answer_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_installation_channels": { + "name": "slack_installation_channels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slack_installation_id": { + "name": "slack_installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slack_installation_channels_installation_id_idx": { + "name": "slack_installation_channels_installation_id_idx", + "columns": [ + { + "expression": "slack_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_installation_channels_slack_installation_id_slack_installations_id_fk": { + "name": "slack_installation_channels_slack_installation_id_slack_installations_id_fk", + "tableFrom": "slack_installation_channels", + "tableTo": "slack_installations", + "columnsFrom": ["slack_installation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "slack_installation_channels_unique": { + "name": "slack_installation_channels_unique", + "nullsNotDistinct": false, + "columns": ["slack_installation_id", "channel_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_installations": { + "name": "slack_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_name": { + "name": "team_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_domain": { + "name": "team_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enterprise_id": { + "name": "enterprise_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enterprise_name": { + "name": "enterprise_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_name": { + "name": "bot_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_name": { + "name": "app_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_access_token": { + "name": "bot_access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_access_token": { + "name": "user_access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'bot'" + }, + "installed_by_user_id": { + "name": "installed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_count_snapshot": { + "name": "member_count_snapshot", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "member_count_snapshot_at": { + "name": "member_count_snapshot_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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": { + "slack_installations_bot_user_id_idx": { + "name": "slack_installations_bot_user_id_idx", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_installations_active_idx": { + "name": "slack_installations_active_idx", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_installations_installed_by_user_id_users_id_fk": { + "name": "slack_installations_installed_by_user_id_users_id_fk", + "tableFrom": "slack_installations", + "tableTo": "users", + "columnsFrom": ["installed_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "slack_installations_team_id_unique": { + "name": "slack_installations_team_id_unique", + "nullsNotDistinct": false, + "columns": ["team_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_quick_answers": { + "name": "slack_quick_answers", + "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 + }, + "slack_channel": { + "name": "slack_channel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_thread_ts": { + "name": "slack_thread_ts", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "messages": { + "name": "messages", + "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": { + "slack_quick_answers_deployment_channel_thread_unique": { + "name": "slack_quick_answers_deployment_channel_thread_unique", + "columns": [ + { + "expression": "slack_channel", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slack_thread_ts", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_quick_answers_deployment_user_idx": { + "name": "slack_quick_answers_deployment_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_quick_answers_user_id_users_id_fk": { + "name": "slack_quick_answers_user_id_users_id_fk", + "tableFrom": "slack_quick_answers", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_user_mappings": { + "name": "slack_user_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "slack_user_mappings_user_id_idx": { + "name": "slack_user_mappings_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_user_mappings_user_id_users_id_fk": { + "name": "slack_user_mappings_user_id_users_id_fk", + "tableFrom": "slack_user_mappings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "slack_user_mappings_unique": { + "name": "slack_user_mappings_unique", + "nullsNotDistinct": false, + "columns": ["slack_user_id", "slack_team_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_artifacts": { + "name": "task_artifacts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "artifact_type": { + "name": "artifact_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "size": { + "name": "size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "uploaded": { + "name": "uploaded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_artifacts_task_id_idx": { + "name": "task_artifacts_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_artifacts_run_id_idx": { + "name": "task_artifacts_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_artifacts_uploaded_idx": { + "name": "task_artifacts_uploaded_idx", + "columns": [ + { + "expression": "uploaded", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_artifacts_created_at_idx": { + "name": "task_artifacts_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_artifacts_path_idx": { + "name": "task_artifacts_path_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_artifacts_task_id_tasks_id_fk": { + "name": "task_artifacts_task_id_tasks_id_fk", + "tableFrom": "task_artifacts", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_artifacts_run_id_task_runs_id_fk": { + "name": "task_artifacts_run_id_task_runs_id_fk", + "tableFrom": "task_artifacts", + "tableTo": "task_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "task_artifacts_task_id_path_version_unique": { + "name": "task_artifacts_task_id_path_version_unique", + "nullsNotDistinct": false, + "columns": ["task_id", "path", "version"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_messages": { + "name": "task_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ts": { + "name": "ts", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protocol": { + "name": "protocol", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_blocks": { + "name": "content_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_messages_task_id_ts_idx": { + "name": "task_messages_task_id_ts_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ts", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_messages_run_id_idx": { + "name": "task_messages_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_messages_created_at_idx": { + "name": "task_messages_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_messages_run_id_task_runs_id_fk": { + "name": "task_messages_run_id_task_runs_id_fk", + "tableFrom": "task_messages", + "tableTo": "task_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_messages_task_id_tasks_id_fk": { + "name": "task_messages_task_id_tasks_id_fk", + "tableFrom": "task_messages", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_messages_user_id_users_id_fk": { + "name": "task_messages_user_id_users_id_fk", + "tableFrom": "task_messages", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "task_messages_task_protocol_ts_event_type_unique": { + "name": "task_messages_task_protocol_ts_event_type_unique", + "nullsNotDistinct": false, + "columns": ["task_id", "protocol", "ts", "event_type"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_pins": { + "name": "task_pins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_pins_deployment_user_task_unique": { + "name": "task_pins_deployment_user_task_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_pins_deployment_user_updated_at_idx": { + "name": "task_pins_deployment_user_updated_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_pins_task_id_idx": { + "name": "task_pins_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_pins_task_id_tasks_id_fk": { + "name": "task_pins_task_id_tasks_id_fk", + "tableFrom": "task_pins", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_pins_user_id_users_id_fk": { + "name": "task_pins_user_id_users_id_fk", + "tableFrom": "task_pins", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_platform_issue_reports": { + "name": "task_platform_issue_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "task_message_id": { + "name": "task_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "report": { + "name": "report", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "slack_posted_at": { + "name": "slack_posted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_platform_issue_reports_created_at_idx": { + "name": "task_platform_issue_reports_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_platform_issue_reports_task_id_created_at_idx": { + "name": "task_platform_issue_reports_task_id_created_at_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_platform_issue_reports_run_id_created_at_idx": { + "name": "task_platform_issue_reports_run_id_created_at_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_platform_issue_reports_task_message_id_unique": { + "name": "task_platform_issue_reports_task_message_id_unique", + "columns": [ + { + "expression": "task_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_platform_issue_reports_task_id_tasks_id_fk": { + "name": "task_platform_issue_reports_task_id_tasks_id_fk", + "tableFrom": "task_platform_issue_reports", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_platform_issue_reports_run_id_task_runs_id_fk": { + "name": "task_platform_issue_reports_run_id_task_runs_id_fk", + "tableFrom": "task_platform_issue_reports", + "tableTo": "task_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_platform_issue_reports_task_message_id_task_messages_id_fk": { + "name": "task_platform_issue_reports_task_message_id_task_messages_id_fk", + "tableFrom": "task_platform_issue_reports", + "tableTo": "task_messages", + "columnsFrom": ["task_message_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_pull_requests": { + "name": "task_pull_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_control_provider": { + "name": "source_control_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pr_title": { + "name": "pr_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_sha": { + "name": "pr_sha", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_base_ref": { + "name": "pr_base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_base_sha": { + "name": "pr_base_sha", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_reaction_id": { + "name": "github_reaction_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "github_check_run_id": { + "name": "github_check_run_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "github_review_comment_id": { + "name": "github_review_comment_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_by_roomote": { + "name": "created_by_roomote", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_handle_feedback_by_user_id": { + "name": "auto_handle_feedback_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detected_at": { + "name": "detected_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_pull_requests_task_id_idx": { + "name": "task_pull_requests_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_pull_requests_repository_id_idx": { + "name": "task_pull_requests_repository_id_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_pull_requests_provider_repository_pr_number_idx": { + "name": "task_pull_requests_provider_repository_pr_number_idx", + "columns": [ + { + "expression": "source_control_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repository", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_pull_requests_task_id_tasks_id_fk": { + "name": "task_pull_requests_task_id_tasks_id_fk", + "tableFrom": "task_pull_requests", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_pull_requests_repository_id_repositories_id_fk": { + "name": "task_pull_requests_repository_id_repositories_id_fk", + "tableFrom": "task_pull_requests", + "tableTo": "repositories", + "columnsFrom": ["repository_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "task_pull_requests_auto_handle_feedback_by_user_id_users_id_fk": { + "name": "task_pull_requests_auto_handle_feedback_by_user_id_users_id_fk", + "tableFrom": "task_pull_requests", + "tableTo": "users", + "columnsFrom": ["auto_handle_feedback_by_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "task_pull_requests_task_pr_unique": { + "name": "task_pull_requests_task_pr_unique", + "nullsNotDistinct": false, + "columns": ["task_id", "pr_url"] + } + }, + "policies": {}, + "checkConstraints": { + "task_pull_requests_source_control_provider_check": { + "name": "task_pull_requests_source_control_provider_check", + "value": "\"task_pull_requests\".\"source_control_provider\" in ('github', 'gitlab', 'gitea', 'ado', 'bitbucket')" + } + }, + "isRLSEnabled": false + }, + "public.task_run_events": { + "name": "task_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_run_events_run_id_created_at_idx": { + "name": "task_run_events_run_id_created_at_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_run_events_task_id_created_at_idx": { + "name": "task_run_events_task_id_created_at_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_run_events_created_at_idx": { + "name": "task_run_events_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_run_events_source_created_at_idx": { + "name": "task_run_events_source_created_at_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_run_events_run_id_task_runs_id_fk": { + "name": "task_run_events_run_id_task_runs_id_fk", + "tableFrom": "task_run_events", + "tableTo": "task_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_run_events_task_id_tasks_id_fk": { + "name": "task_run_events_task_id_tasks_id_fk", + "tableFrom": "task_run_events", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_runs": { + "name": "task_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "task_runs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'fresh'" + }, + "source_run_id": { + "name": "source_run_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "acting_user_id": { + "name": "acting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_kind": { + "name": "payload_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "harness": { + "name": "harness", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'opencode-server'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queue_scope": { + "name": "queue_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_phase": { + "name": "task_phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log": { + "name": "log", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "artifacts": { + "name": "artifacts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sandbox_cmd_id": { + "name": "sandbox_cmd_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "machine_domain": { + "name": "machine_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "machine_domains": { + "name": "machine_domains", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "initial_paths": { + "name": "initial_paths", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "primary_port_name": { + "name": "primary_port_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sandbox_server_url": { + "name": "sandbox_server_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proxy_ports": { + "name": "proxy_ports", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "worker_release_tag": { + "name": "worker_release_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "worker_version": { + "name": "worker_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "worker_commit": { + "name": "worker_commit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vendor": { + "name": "vendor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "configured_vcpus": { + "name": "configured_vcpus", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "configured_cpu_cores": { + "name": "configured_cpu_cores", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "configured_memory_mib": { + "name": "configured_memory_mib", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "snapshot_id": { + "name": "snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot_requested_at": { + "name": "snapshot_requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "snapshot_created_at": { + "name": "snapshot_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "snapshot_failed_at": { + "name": "snapshot_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "keepalive_ms": { + "name": "keepalive_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sleep_at": { + "name": "sleep_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sleep_requested_at": { + "name": "sleep_requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "worker_heartbeat_at": { + "name": "worker_heartbeat_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_snapshot_id": { + "name": "source_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_bypass_value": { + "name": "auth_bypass_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_bypass_header_name": { + "name": "auth_bypass_header_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "dequeued_at": { + "name": "dequeued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "provision_started_at": { + "name": "provision_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "provision_ready_at": { + "name": "provision_ready_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "setup_completed_at": { + "name": "setup_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "environment_setup_state": { + "name": "environment_setup_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment_setup_completed_at": { + "name": "environment_setup_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "harness_started_at": { + "name": "harness_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "runtime_task_started_at": { + "name": "runtime_task_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "first_assistant_output_at": { + "name": "first_assistant_output_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_requested_at": { + "name": "cancel_requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "launch_mode": { + "name": "launch_mode", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "task_runs_task_id_idx": { + "name": "task_runs_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_queue_scope_idx": { + "name": "task_runs_queue_scope_idx", + "columns": [ + { + "expression": "queue_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_acting_user_id_idx": { + "name": "task_runs_acting_user_id_idx", + "columns": [ + { + "expression": "acting_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_snapshot_id_idx": { + "name": "task_runs_snapshot_id_idx", + "columns": [ + { + "expression": "snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_sleep_at_idx": { + "name": "task_runs_sleep_at_idx", + "columns": [ + { + "expression": "sleep_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_worker_heartbeat_at_idx": { + "name": "task_runs_worker_heartbeat_at_idx", + "columns": [ + { + "expression": "worker_heartbeat_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_sleep_check_due_v2_idx": { + "name": "task_runs_sleep_check_due_v2_idx", + "columns": [ + { + "expression": "sleep_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"task_runs\".\"status\" IN ('running', 'idle') AND \"task_runs\".\"machine_id\" IS NOT NULL AND \"task_runs\".\"sleep_at\" IS NOT NULL AND \"task_runs\".\"sleep_requested_at\" IS NULL AND \"task_runs\".\"snapshot_id\" IS NULL AND \"task_runs\".\"snapshot_requested_at\" IS NULL AND \"task_runs\".\"vendor\" IN ('modal', 'daytona', 'e2b', 'docker', 'blaxel', 'roomote', 'azure')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_sleep_check_stale_worker_v2_idx": { + "name": "task_runs_sleep_check_stale_worker_v2_idx", + "columns": [ + { + "expression": "worker_heartbeat_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"task_runs\".\"status\" IN ('running', 'idle') AND \"task_runs\".\"machine_id\" IS NOT NULL AND \"task_runs\".\"worker_heartbeat_at\" IS NOT NULL AND \"task_runs\".\"sleep_requested_at\" IS NULL AND \"task_runs\".\"snapshot_id\" IS NULL AND \"task_runs\".\"snapshot_requested_at\" IS NULL AND \"task_runs\".\"vendor\" IN ('modal', 'daytona', 'e2b', 'docker', 'blaxel', 'roomote', 'azure')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_sleep_check_active_v2_idx": { + "name": "task_runs_sleep_check_active_v2_idx", + "columns": [ + { + "expression": "vendor", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"task_runs\".\"status\" IN ('running', 'idle') AND \"task_runs\".\"machine_id\" IS NOT NULL AND \"task_runs\".\"sleep_requested_at\" IS NULL AND \"task_runs\".\"snapshot_id\" IS NULL AND \"task_runs\".\"snapshot_requested_at\" IS NULL AND \"task_runs\".\"vendor\" IN ('modal', 'daytona', 'e2b', 'docker', 'blaxel', 'roomote', 'azure')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_source_snapshot_id_idx": { + "name": "task_runs_source_snapshot_id_idx", + "columns": [ + { + "expression": "source_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_source_run_id_idx": { + "name": "task_runs_source_run_id_idx", + "columns": [ + { + "expression": "source_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_discord_source_event_unique": { + "name": "task_runs_discord_source_event_unique", + "columns": [ + { + "expression": "(\"payload\"->>'communicationSourceEventId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"task_runs\".\"payload\"->>'communicationProvider' = 'discord' AND \"task_runs\".\"payload\"->>'communicationSourceEventId' IS NOT NULL AND \"task_runs\".\"canceled_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_runs_first_assistant_output_at_idx": { + "name": "task_runs_first_assistant_output_at_idx", + "columns": [ + { + "expression": "first_assistant_output_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_runs_task_id_tasks_id_fk": { + "name": "task_runs_task_id_tasks_id_fk", + "tableFrom": "task_runs", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_runs_source_run_id_task_runs_id_fk": { + "name": "task_runs_source_run_id_task_runs_id_fk", + "tableFrom": "task_runs", + "tableTo": "task_runs", + "columnsFrom": ["source_run_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "task_runs_acting_user_id_users_id_fk": { + "name": "task_runs_acting_user_id_users_id_fk", + "tableFrom": "task_runs", + "tableTo": "users", + "columnsFrom": ["acting_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "task_runs_kind_check": { + "name": "task_runs_kind_check", + "value": "\"task_runs\".\"kind\" in ('fresh', 'resume')" + }, + "task_runs_harness_check": { + "name": "task_runs_harness_check", + "value": "\"task_runs\".\"harness\" in ('opencode-server')" + } + }, + "isRLSEnabled": false + }, + "public.task_slack_reply_details": { + "name": "task_slack_reply_details", + "schema": "", + "columns": { + "detail_id": { + "name": "detail_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "findings": { + "name": "findings", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_slack_reply_details_task_id_idx": { + "name": "task_slack_reply_details_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_slack_reply_details_deployment_task_detail_unique": { + "name": "task_slack_reply_details_deployment_task_detail_unique", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "detail_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_slack_reply_details_task_id_tasks_id_fk": { + "name": "task_slack_reply_details_task_id_tasks_id_fk", + "tableFrom": "task_slack_reply_details", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_start_parallel_counts": { + "name": "task_start_parallel_counts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "payload_kind": { + "name": "payload_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parallel_count": { + "name": "parallel_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "activity_window_seconds": { + "name": "activity_window_seconds", + "type": "integer", + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_start_parallel_counts_run_id_unique": { + "name": "task_start_parallel_counts_run_id_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_start_parallel_counts_task_id_started_at_idx": { + "name": "task_start_parallel_counts_task_id_started_at_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_start_parallel_counts_started_at_idx": { + "name": "task_start_parallel_counts_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_start_parallel_counts_task_id_tasks_id_fk": { + "name": "task_start_parallel_counts_task_id_tasks_id_fk", + "tableFrom": "task_start_parallel_counts", + "tableTo": "tasks", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_start_parallel_counts_run_id_task_runs_id_fk": { + "name": "task_start_parallel_counts_run_id_task_runs_id_fk", + "tableFrom": "task_start_parallel_counts", + "tableTo": "task_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow": { + "name": "workflow", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'visible'" + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "initiator_kind": { + "name": "initiator_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "initiator_user_id": { + "name": "initiator_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "initiator_automation": { + "name": "initiator_automation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_external_id": { + "name": "actor_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_display_name": { + "name": "actor_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "commit_author_kind": { + "name": "commit_author_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "commit_author_user_id": { + "name": "commit_author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "commit_author_login": { + "name": "commit_author_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "commit_author_external_id": { + "name": "commit_author_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_assignee_login": { + "name": "pr_assignee_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_thread_ts": { + "name": "slack_thread_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linear_session_id": { + "name": "linear_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linear_issue_id": { + "name": "linear_issue_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linear_organization_id": { + "name": "linear_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "harness": { + "name": "harness", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'opencode-server'" + }, + "harness_session_id": { + "name": "harness_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_provider": { + "name": "model_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title_edited_by_user_at": { + "name": "title_edited_by_user_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "llm_title_checkpoint": { + "name": "llm_title_checkpoint", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "draft_prompt": { + "name": "draft_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_work_kind": { + "name": "requested_work_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "requested_work_kind_source": { + "name": "requested_work_kind_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system_default'" + }, + "requested_work_kind_confidence": { + "name": "requested_work_kind_confidence", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "harness_instructions": { + "name": "harness_instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "compute_duration_ms": { + "name": "compute_duration_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "activity_at": { + "name": "activity_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "repository_url": { + "name": "repository_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_name": { + "name": "repository_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "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": { + "tasks_initiator_user_id_idx": { + "name": "tasks_initiator_user_id_idx", + "columns": [ + { + "expression": "initiator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_initiator_automation_idx": { + "name": "tasks_initiator_automation_idx", + "columns": [ + { + "expression": "initiator_automation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_workflow_idx": { + "name": "tasks_workflow_idx", + "columns": [ + { + "expression": "workflow", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_visibility_activity_at_idx": { + "name": "tasks_visibility_activity_at_idx", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "activity_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_harness_session_id_idx": { + "name": "tasks_harness_session_id_idx", + "columns": [ + { + "expression": "harness_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_timestamp_idx": { + "name": "tasks_timestamp_idx", + "columns": [ + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_deployment_activity_at_idx": { + "name": "tasks_deployment_activity_at_idx", + "columns": [ + { + "expression": "activity_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_initiator_user_id_users_id_fk": { + "name": "tasks_initiator_user_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": ["initiator_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_initiator_automation_automations_key_fk": { + "name": "tasks_initiator_automation_automations_key_fk", + "tableFrom": "tasks", + "tableTo": "automations", + "columnsFrom": ["initiator_automation"], + "columnsTo": ["key"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_commit_author_user_id_users_id_fk": { + "name": "tasks_commit_author_user_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": ["commit_author_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "tasks_initiator_shape_check": { + "name": "tasks_initiator_shape_check", + "value": "(\"tasks\".\"initiator_kind\" = 'user' AND \"tasks\".\"initiator_automation\" IS NULL AND (\"tasks\".\"initiator_user_id\" IS NOT NULL OR \"tasks\".\"actor_external_id\" IS NOT NULL)) OR (\"tasks\".\"initiator_kind\" = 'automation' AND \"tasks\".\"initiator_automation\" IS NOT NULL AND \"tasks\".\"initiator_user_id\" IS NULL)" + }, + "tasks_workflow_check": { + "name": "tasks_workflow_check", + "value": "\"tasks\".\"workflow\" in ('standard', 'pr_review', 'pr_conflict_resolve', 'scan', 'mcp_recommendations', 'setup_onboarding', 'env_snapshot', 'eval')" + }, + "tasks_surface_check": { + "name": "tasks_surface_check", + "value": "\"tasks\".\"surface\" in ('web', 'api', 'slack', 'teams', 'telegram', 'discord', 'linear', 'github', 'gitlab', 'gitea', 'ado', 'bitbucket', 'system')" + }, + "tasks_trigger_check": { + "name": "tasks_trigger_check", + "value": "\"tasks\".\"trigger\" in ('message', 'webhook', 'schedule', 'manual')" + }, + "tasks_visibility_check": { + "name": "tasks_visibility_check", + "value": "\"tasks\".\"visibility\" in ('visible', 'hidden')" + }, + "tasks_state_check": { + "name": "tasks_state_check", + "value": "\"tasks\".\"state\" in ('active', 'completed', 'failed', 'canceled')" + }, + "tasks_harness_check": { + "name": "tasks_harness_check", + "value": "\"tasks\".\"harness\" in ('opencode-server')" + }, + "tasks_requested_work_kind_check": { + "name": "tasks_requested_work_kind_check", + "value": "\"tasks\".\"requested_work_kind\" in ('question', 'plan', 'implement', 'unknown')" + }, + "tasks_requested_work_kind_source_check": { + "name": "tasks_requested_work_kind_source_check", + "value": "\"tasks\".\"requested_work_kind_source\" in ('explicit_bootstrap', 'task_tool', 'llm_classifier', 'inherited', 'system_default')" + }, + "tasks_commit_author_kind_check": { + "name": "tasks_commit_author_kind_check", + "value": "\"tasks\".\"commit_author_kind\" IS NULL OR \"tasks\".\"commit_author_kind\" in ('roomote', 'user', 'external')" + } + }, + "isRLSEnabled": false + }, + "public.teams_installations": { + "name": "teams_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "installation_key": { + "name": "installation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_name": { + "name": "team_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel_name": { + "name": "channel_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_type": { + "name": "conversation_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_app_id": { + "name": "bot_app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bot_name": { + "name": "bot_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_url": { + "name": "service_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_activity_at": { + "name": "last_activity_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": { + "teams_installations_tenant_id_idx": { + "name": "teams_installations_tenant_id_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "teams_installations_team_id_idx": { + "name": "teams_installations_team_id_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "teams_installations_conversation_id_idx": { + "name": "teams_installations_conversation_id_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "teams_installations_active_idx": { + "name": "teams_installations_active_idx", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_installations_installation_key_unique": { + "name": "teams_installations_installation_key_unique", + "nullsNotDistinct": false, + "columns": ["installation_key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams_user_mappings": { + "name": "teams_user_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "teams_user_id": { + "name": "teams_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "teams_tenant_id": { + "name": "teams_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "teams_aad_object_id": { + "name": "teams_aad_object_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_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": { + "teams_user_mappings_aad_object_idx": { + "name": "teams_user_mappings_aad_object_idx", + "columns": [ + { + "expression": "teams_aad_object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "teams_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "teams_user_mappings_user_id_idx": { + "name": "teams_user_mappings_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "teams_user_mappings_user_id_users_id_fk": { + "name": "teams_user_mappings_user_id_users_id_fk", + "tableFrom": "teams_user_mappings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_user_mappings_unique": { + "name": "teams_user_mappings_unique", + "nullsNotDistinct": false, + "columns": ["teams_user_id", "teams_tenant_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.telegram_user_mappings": { + "name": "telegram_user_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "telegram_user_id": { + "name": "telegram_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "telegram_username": { + "name": "telegram_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_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": { + "telegram_user_mappings_user_id_idx": { + "name": "telegram_user_mappings_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "telegram_user_mappings_user_id_users_id_fk": { + "name": "telegram_user_mappings_user_id_users_id_fk", + "tableFrom": "telegram_user_mappings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "telegram_user_mappings_unique": { + "name": "telegram_user_mappings_unique", + "nullsNotDistinct": false, + "columns": ["telegram_user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tracked_messages": { + "name": "tracked_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_ts": { + "name": "message_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thread_ts": { + "name": "thread_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "work_item_id": { + "name": "work_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "automation_key": { + "name": "automation_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary_text": { + "name": "summary_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "posted_at": { + "name": "posted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "dismissed_at": { + "name": "dismissed_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": { + "tracked_messages_kind_dedupe_key_unique": { + "name": "tracked_messages_kind_dedupe_key_unique", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tracked_messages_work_item_id_idx": { + "name": "tracked_messages_work_item_id_idx", + "columns": [ + { + "expression": "work_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tracked_messages_channel_message_idx": { + "name": "tracked_messages_channel_message_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_ts", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tracked_messages_automation_channel_posted_idx": { + "name": "tracked_messages_automation_channel_posted_idx", + "columns": [ + { + "expression": "automation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "posted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tracked_messages_work_item_id_work_items_id_fk": { + "name": "tracked_messages_work_item_id_work_items_id_fk", + "tableFrom": "tracked_messages", + "tableTo": "work_items", + "columnsFrom": ["work_item_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tracked_messages_automation_key_automations_key_fk": { + "name": "tracked_messages_automation_key_automations_key_fk", + "tableFrom": "tracked_messages", + "tableTo": "automations", + "columnsFrom": ["automation_key"], + "columnsTo": ["key"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tracked_messages_created_by_user_id_users_id_fk": { + "name": "tracked_messages_created_by_user_id_users_id_fk", + "tableFrom": "tracked_messages", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_api_keys": { + "name": "user_api_keys", + "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 + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_key": { + "name": "api_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": { + "user_api_keys_user_id_idx": { + "name": "user_api_keys_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_api_keys_user_deployment_provider_unique": { + "name": "user_api_keys_user_deployment_provider_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_api_keys_user_id_users_id_fk": { + "name": "user_api_keys_user_id_users_id_fk", + "tableFrom": "user_api_keys", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "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 + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity": { + "name": "entity", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "analytics_id": { + "name": "analytics_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cookie_consented_at": { + "name": "cookie_consented_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "onboarding_completed_at": { + "name": "onboarding_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by_invite_id": { + "name": "invited_by_invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "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": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_created_at_idx": { + "name": "users_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_analytics_id_unique_idx": { + "name": "users_analytics_id_unique_idx", + "columns": [ + { + "expression": "analytics_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhooks": { + "name": "webhooks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "delivery_id": { + "name": "delivery_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "succeeded_at": { + "name": "succeeded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failed_at": { + "name": "failed_at", + "type": "timestamp", + "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()" + } + }, + "indexes": { + "webhooks_provider_delivery_id_unique": { + "name": "webhooks_provider_delivery_id_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhooks_event_idx": { + "name": "webhooks_event_idx", + "columns": [ + { + "expression": "event", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhooks_created_at_idx": { + "name": "webhooks_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": { + "webhooks_status_exclusive": { + "name": "webhooks_status_exclusive", + "value": "(\n (succeeded_at IS NOT NULL)::int +\n (failed_at IS NOT NULL)::int\n ) <= 1" + } + }, + "isRLSEnabled": false + }, + "public.work_items": { + "name": "work_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "automation_key": { + "name": "automation_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_id": { + "name": "source_task_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "selected_by_user_id": { + "name": "selected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_work_item_id": { + "name": "source_work_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "brief": { + "name": "brief", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_prompt": { + "name": "execution_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "investigation_context": { + "name": "investigation_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_kind": { + "name": "action_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disposition": { + "name": "disposition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "repository_ids": { + "name": "repository_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "target_repository_full_name": { + "name": "target_repository_full_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_environment_id": { + "name": "target_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_readiness": { + "name": "workspace_readiness", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "readiness_message": { + "name": "readiness_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "launch_claimed_at": { + "name": "launch_claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "launched_task_id": { + "name": "launched_task_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "launched_at": { + "name": "launched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failed_at": { + "name": "failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "launch_error": { + "name": "launch_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dismissed_at": { + "name": "dismissed_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": { + "work_items_source_task_idx": { + "name": "work_items_source_task_idx", + "columns": [ + { + "expression": "source_task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "work_items_kind_status_idx": { + "name": "work_items_kind_status_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "work_items_automation_key_fingerprint_idx": { + "name": "work_items_automation_key_fingerprint_idx", + "columns": [ + { + "expression": "automation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "work_items_fingerprint_idx": { + "name": "work_items_fingerprint_idx", + "columns": [ + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "work_items_launched_task_id_idx": { + "name": "work_items_launched_task_id_idx", + "columns": [ + { + "expression": "launched_task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "work_items_source_task_kind_sort_order_unique": { + "name": "work_items_source_task_kind_sort_order_unique", + "columns": [ + { + "expression": "source_task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "work_items_automation_key_automations_key_fk": { + "name": "work_items_automation_key_automations_key_fk", + "tableFrom": "work_items", + "tableTo": "automations", + "columnsFrom": ["automation_key"], + "columnsTo": ["key"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "work_items_source_task_id_tasks_id_fk": { + "name": "work_items_source_task_id_tasks_id_fk", + "tableFrom": "work_items", + "tableTo": "tasks", + "columnsFrom": ["source_task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "work_items_selected_by_user_id_users_id_fk": { + "name": "work_items_selected_by_user_id_users_id_fk", + "tableFrom": "work_items", + "tableTo": "users", + "columnsFrom": ["selected_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "work_items_source_work_item_id_work_items_id_fk": { + "name": "work_items_source_work_item_id_work_items_id_fk", + "tableFrom": "work_items", + "tableTo": "work_items", + "columnsFrom": ["source_work_item_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "work_items_target_environment_id_environments_id_fk": { + "name": "work_items_target_environment_id_environments_id_fk", + "tableFrom": "work_items", + "tableTo": "environments", + "columnsFrom": ["target_environment_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "work_items_launched_task_id_tasks_id_fk": { + "name": "work_items_launched_task_id_tasks_id_fk", + "tableFrom": "work_items", + "tableTo": "tasks", + "columnsFrom": ["launched_task_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index beabfe8e7..c50adcae9 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -197,6 +197,13 @@ "when": 1785686208286, "tag": "0027_strange_the_hunter", "breakpoints": true + }, + { + "idx": 28, + "version": "7", + "when": 1785726662910, + "tag": "0028_odd_wolfsbane", + "breakpoints": true } ] } diff --git a/packages/db/src/lib/__tests__/model-runtime-config.test.ts b/packages/db/src/lib/__tests__/model-runtime-config.test.ts index ea479eac4..595daed88 100644 --- a/packages/db/src/lib/__tests__/model-runtime-config.test.ts +++ b/packages/db/src/lib/__tests__/model-runtime-config.test.ts @@ -1,10 +1,15 @@ -const { mockResolveDeploymentEnvVar } = vi.hoisted(() => ({ - mockResolveDeploymentEnvVar: vi.fn(), -})); - -vi.mock('../environment-variables', async (importOriginal) => ({ - ...(await importOriginal()), - resolveDeploymentEnvVar: mockResolveDeploymentEnvVar, +const { mockGetPersistedModelProviderEnvironmentVariableValues } = vi.hoisted( + () => ({ + mockGetPersistedModelProviderEnvironmentVariableValues: vi.fn(), + }), +); + +vi.mock('../model-provider-environment-variables', async (importOriginal) => ({ + ...(await importOriginal< + typeof import('../model-provider-environment-variables') + >()), + getPersistedModelProviderEnvironmentVariableValues: + mockGetPersistedModelProviderEnvironmentVariableValues, })); import { resolveModelProviderEnvValue } from '../model-runtime-config'; @@ -16,7 +21,9 @@ describe('resolveModelProviderEnvValue', () => { }); it('checks all runtime aliases before querying persisted values', async () => { - mockResolveDeploymentEnvVar.mockResolvedValue('persisted-key'); + mockGetPersistedModelProviderEnvironmentVariableValues.mockResolvedValue({ + GEMINI_API_KEY: 'persisted-key', + }); const value = await resolveModelProviderEnvValue( ['GEMINI_API_KEY', 'GOOGLE_GENERATIVE_AI_API_KEY'], @@ -26,12 +33,16 @@ describe('resolveModelProviderEnvValue', () => { ); expect(value).toBe('runtime-alias-key'); - expect(mockResolveDeploymentEnvVar).not.toHaveBeenCalled(); + expect( + mockGetPersistedModelProviderEnvironmentVariableValues, + ).not.toHaveBeenCalled(); }); it('looks up only the requested persisted key', async () => { const executor = {} as DatabaseOrTransaction; - mockResolveDeploymentEnvVar.mockResolvedValue('persisted-key'); + mockGetPersistedModelProviderEnvironmentVariableValues.mockResolvedValue({ + OPENAI_API_KEY: 'persisted-key', + }); const value = await resolveModelProviderEnvValue('OPENAI_API_KEY', { runtimeEnv: {}, @@ -39,10 +50,8 @@ describe('resolveModelProviderEnvValue', () => { }); expect(value).toBe('persisted-key'); - expect(mockResolveDeploymentEnvVar).toHaveBeenCalledWith( - 'OPENAI_API_KEY', - executor, - {}, - ); + expect( + mockGetPersistedModelProviderEnvironmentVariableValues, + ).toHaveBeenCalledWith(['OPENAI_API_KEY'], executor); }); }); diff --git a/packages/db/src/lib/model-provider-environment-variables.test.ts b/packages/db/src/lib/model-provider-environment-variables.test.ts new file mode 100644 index 000000000..6c0326981 --- /dev/null +++ b/packages/db/src/lib/model-provider-environment-variables.test.ts @@ -0,0 +1,117 @@ +import { db } from '../db'; +import { + environmentVariables, + modelProviderEnvironmentVariables, +} from '../schema'; + +import { + getPersistedModelProviderEnvironmentVariableNames, + getPersistedModelProviderEnvironmentVariableValues, +} from './model-provider-environment-variables'; + +describe('model provider environment variables', () => { + beforeEach(async () => { + await db.delete(modelProviderEnvironmentVariables); + await db.delete(environmentVariables); + }); + + it('falls back to legacy model-provider rows during the N-1 rollout', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + + await db.insert(environmentVariables).values({ + userId: null, + name: 'TOGETHER_API_KEY', + value: 'legacy-key', + createdByUserId: null, + lastUpdatedByUserId: null, + }); + + await expect( + getPersistedModelProviderEnvironmentVariableValues(['TOGETHER_API_KEY']), + ).resolves.toEqual({ TOGETHER_API_KEY: 'legacy-key' }); + await expect( + getPersistedModelProviderEnvironmentVariableNames(), + ).resolves.toContain('TOGETHER_API_KEY'); + expect(warn).toHaveBeenCalledWith( + expect.stringContaining( + 'Using legacy persisted model-provider value name=TOGETHER_API_KEY', + ), + ); + warn.mockRestore(); + }); + + it('prefers the dedicated model-provider value over its legacy copy', async () => { + await Promise.all([ + db.insert(environmentVariables).values({ + userId: null, + name: 'TOGETHER_API_KEY', + value: 'legacy-key', + createdByUserId: null, + lastUpdatedByUserId: null, + }), + db.insert(modelProviderEnvironmentVariables).values({ + name: 'TOGETHER_API_KEY', + value: 'dedicated-key', + createdByUserId: null, + lastUpdatedByUserId: null, + }), + ]); + + await expect( + getPersistedModelProviderEnvironmentVariableValues(['TOGETHER_API_KEY']), + ).resolves.toEqual({ TOGETHER_API_KEY: 'dedicated-key' }); + }); + + it('ignores unrelated legacy task variables but keeps declared custom model keys', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + await db.insert(environmentVariables).values([ + { + userId: null, + name: 'R_MODEL_ENV_KEYS', + value: 'CUSTOM_LLM_TOKEN', + createdByUserId: null, + lastUpdatedByUserId: null, + }, + { + userId: null, + name: 'CUSTOM_LLM_TOKEN', + value: 'model-token', + createdByUserId: null, + lastUpdatedByUserId: null, + }, + { + userId: null, + name: 'STRIPE_API_KEY', + value: 'task-token', + createdByUserId: null, + lastUpdatedByUserId: null, + }, + ]); + + await expect( + getPersistedModelProviderEnvironmentVariableValues([ + 'CUSTOM_LLM_TOKEN', + 'STRIPE_API_KEY', + ]), + ).resolves.toEqual({ CUSTOM_LLM_TOKEN: 'model-token' }); + expect(warn).not.toHaveBeenCalledWith( + expect.stringContaining('STRIPE_API_KEY'), + ); + warn.mockRestore(); + }); + + it('backfills built-in and named OpenAI-compatible rows without deleting legacy data', async () => { + const migration = await readFile( + new URL('../../drizzle/0028_odd_wolfsbane.sql', import.meta.url), + 'utf8', + ); + + expect(migration).toContain( + 'INSERT INTO "model_provider_environment_variables"', + ); + expect(migration).toContain("'TOGETHER_API_KEY'"); + expect(migration).toContain('"name" LIKE \'OPENAI_COMPATIBLE_%_API_KEY\''); + expect(migration).not.toContain('DELETE FROM "environment_variables"'); + }); +}); +import { readFile } from 'node:fs/promises'; diff --git a/packages/db/src/lib/model-provider-environment-variables.ts b/packages/db/src/lib/model-provider-environment-variables.ts new file mode 100644 index 000000000..4f40167c0 --- /dev/null +++ b/packages/db/src/lib/model-provider-environment-variables.ts @@ -0,0 +1,162 @@ +import { inArray } from 'drizzle-orm'; +import { + DEFAULT_MODEL_PROVIDER_ENV_KEYS, + isOpenAiCompatibleProviderEnvVarName, + parseModelProviderEnvKeys, +} from '@roomote/types'; + +import { type DatabaseOrTransaction, db } from '../db'; +import { decryptSecrets } from '../encryption'; +import { + environmentVariables, + modelProviderEnvironmentVariables, +} from '../schema'; + +import { stringifyDecryptedEnvVarValue } from './environment-variables'; + +const reportedLegacyFallbackNames = new Set(); + +function reportLegacyFallbackNames( + modelNames: ReadonlySet, + legacyNames: readonly string[], +) { + for (const name of legacyNames) { + if (modelNames.has(name) || reportedLegacyFallbackNames.has(name)) { + continue; + } + + reportedLegacyFallbackNames.add(name); + console.warn( + `[model-provider-env] Using legacy persisted model-provider value name=${name}; re-save it under Settings > Models to migrate it.`, + ); + } +} + +function filterRecognizedLegacyValues( + modelValues: Record, + legacyValues: Record, +): Record { + const configuredLegacyNames = new Set( + parseModelProviderEnvKeys( + modelValues.R_MODEL_ENV_KEYS ?? legacyValues.R_MODEL_ENV_KEYS, + ), + ); + + return Object.fromEntries( + Object.entries(legacyValues).filter( + ([name]) => + DEFAULT_MODEL_PROVIDER_ENV_KEYS.includes(name) || + name === 'R_MODEL_ENV_KEYS' || + isOpenAiCompatibleProviderEnvVarName(name) || + configuredLegacyNames.has(name), + ), + ); +} + +async function decryptRows( + rows: Array<{ name: string; value: string | null }>, +): Promise> { + const values: Record = {}; + + for (const row of rows) { + const decryptedValue = await decryptSecrets(row.value); + const value = stringifyDecryptedEnvVarValue(decryptedValue).trim(); + + if (value) { + values[row.name] = value; + } + } + + return values; +} + +export async function getPersistedModelProviderEnvironmentVariableNames( + executor: DatabaseOrTransaction = db, +): Promise { + return Object.keys( + await getPersistedModelProviderEnvironmentVariables(executor), + ); +} + +export async function getPersistedModelProviderEnvironmentVariableValues( + names: readonly string[], + executor: DatabaseOrTransaction = db, +): Promise> { + if (names.length === 0) { + return {}; + } + + const queryNames = [...new Set([...names, 'R_MODEL_ENV_KEYS'])]; + const [modelRows, legacyRows] = await Promise.all([ + executor + .select({ + name: modelProviderEnvironmentVariables.name, + value: modelProviderEnvironmentVariables.value, + }) + .from(modelProviderEnvironmentVariables) + .where(inArray(modelProviderEnvironmentVariables.name, queryNames)), + executor + .select({ + name: environmentVariables.name, + value: environmentVariables.value, + }) + .from(environmentVariables) + .where(inArray(environmentVariables.name, queryNames)), + ]); + const [modelValues, legacyValues] = await Promise.all([ + decryptRows(modelRows), + decryptRows(legacyRows), + ]); + const recognizedLegacyValues = filterRecognizedLegacyValues( + modelValues, + legacyValues, + ); + reportLegacyFallbackNames( + new Set(Object.keys(modelValues)), + Object.keys(recognizedLegacyValues), + ); + const values = { ...recognizedLegacyValues, ...modelValues }; + + return Object.fromEntries( + names.flatMap((name) => + values[name] === undefined ? [] : [[name, values[name]]], + ), + ); +} + +/** + * Loads model-provider values from the dedicated store, then fills missing + * names from the legacy table for the N-1 compatibility release. + */ +export async function getPersistedModelProviderEnvironmentVariables( + executor: DatabaseOrTransaction = db, +): Promise> { + const [modelRows, legacyRows] = await Promise.all([ + executor + .select({ + name: modelProviderEnvironmentVariables.name, + value: modelProviderEnvironmentVariables.value, + }) + .from(modelProviderEnvironmentVariables), + executor + .select({ + name: environmentVariables.name, + value: environmentVariables.value, + }) + .from(environmentVariables), + ]); + const [modelValues, legacyValues] = await Promise.all([ + decryptRows(modelRows), + decryptRows(legacyRows), + ]); + const recognizedLegacyValues = filterRecognizedLegacyValues( + modelValues, + legacyValues, + ); + reportLegacyFallbackNames( + new Set(Object.keys(modelValues)), + Object.keys(recognizedLegacyValues), + ); + + return { ...recognizedLegacyValues, ...modelValues }; +} diff --git a/packages/db/src/lib/model-runtime-config.test.ts b/packages/db/src/lib/model-runtime-config.test.ts index ba5d3099f..ee1887e1a 100644 --- a/packages/db/src/lib/model-runtime-config.test.ts +++ b/packages/db/src/lib/model-runtime-config.test.ts @@ -6,6 +6,8 @@ const { mockIsChatGptSubscriptionFastModeEnabled, mockResolveGitHubCopilotOpenCodeAuthContent, mockGetFreshXaiAccessToken, + mockGetPersistedModelProviderEnvironmentVariables, + mockGetPersistedModelProviderEnvironmentVariableValues, } = vi.hoisted(() => ({ mockDecryptSecrets: vi.fn(), mockDeploymentSettingsFindFirst: vi.fn(), @@ -14,6 +16,8 @@ const { mockIsChatGptSubscriptionFastModeEnabled: vi.fn(), mockResolveGitHubCopilotOpenCodeAuthContent: vi.fn(), mockGetFreshXaiAccessToken: vi.fn(), + mockGetPersistedModelProviderEnvironmentVariables: vi.fn(), + mockGetPersistedModelProviderEnvironmentVariableValues: vi.fn(), })); vi.mock('../encryption', () => ({ @@ -39,6 +43,13 @@ vi.mock('./environment-variables', () => ({ stringifyDecryptedEnvVarValue: (value: unknown) => String(value), })); +vi.mock('./model-provider-environment-variables', () => ({ + getPersistedModelProviderEnvironmentVariables: (...args: unknown[]) => + mockGetPersistedModelProviderEnvironmentVariables(...args), + getPersistedModelProviderEnvironmentVariableValues: (...args: unknown[]) => + mockGetPersistedModelProviderEnvironmentVariableValues(...args), +})); + vi.mock('./chatgpt-subscription', () => ({ resolveOpenCodeAuthContent: (...args: unknown[]) => mockResolveOpenCodeAuthContent(...args), @@ -75,6 +86,10 @@ describe('resolveEffectiveModelRuntimeEnv', () => { mockResolveOpenCodeAuthContent.mockResolvedValue(null); mockIsChatGptSubscriptionFastModeEnabled.mockResolvedValue(false); mockGetFreshXaiAccessToken.mockResolvedValue(null); + mockGetPersistedModelProviderEnvironmentVariables.mockResolvedValue({}); + mockGetPersistedModelProviderEnvironmentVariableValues.mockResolvedValue( + {}, + ); }); it('prefers real runtime env values over persisted deployment config', async () => { @@ -91,7 +106,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { R_VISION_MODEL: 'openai/gpt-5.5', OPENAI_API_KEY: 'sk-runtime', }, - deploymentEnvVars: { + modelProviderEnvVars: { OPENAI_API_KEY: 'sk-saved', ANTHROPIC_API_KEY: 'sk-anthropic', }, @@ -119,19 +134,17 @@ describe('resolveEffectiveModelRuntimeEnv', () => { roomoteVisionModel: null, }, }); - mockEnvironmentVariablesFindMany.mockResolvedValue([ - { - name: 'ANTHROPIC_API_KEY', - value: 'sk-encrypted', - }, - ]); - mockDecryptSecrets.mockResolvedValue('sk-persisted'); + mockGetPersistedModelProviderEnvironmentVariables.mockResolvedValue({ + ANTHROPIC_API_KEY: 'sk-persisted', + }); const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, }); - expect(mockEnvironmentVariablesFindMany).toHaveBeenCalledTimes(1); + expect( + mockGetPersistedModelProviderEnvironmentVariables, + ).toHaveBeenCalledTimes(1); expect(env).toEqual({ R_MODEL: 'anthropic/claude-sonnet-4', R_MODEL_REASONING_EFFORT: 'medium', @@ -153,7 +166,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { R_MODEL_ENV_KEYS: 'CUSTOM_LLM_TOKEN', CUSTOM_LLM_TOKEN: 'saved-token', }, @@ -177,7 +190,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', }, }); @@ -209,7 +222,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { R_SMALL_MODEL: 'openrouter/anthropic/claude-sonnet-4', OPENROUTER_API_KEY: 'sk-runtime', }, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-saved', }, }); @@ -232,7 +245,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', }, }); @@ -252,7 +265,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', }, }); @@ -284,7 +297,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', }, }); @@ -315,7 +328,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', ANTHROPIC_API_KEY: 'sk-anthropic', }, @@ -347,7 +360,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', ANTHROPIC_API_KEY: 'sk-anthropic', }, @@ -379,7 +392,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { runtimeEnv: { R_PLANNING_MODEL: 'openrouter/anthropic/claude-opus-4.7', }, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', }, }); @@ -405,7 +418,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { runtimeEnv: { R_CODE_REVIEW_MODEL: 'openrouter/anthropic/claude-sonnet-4', }, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', }, }); @@ -429,7 +442,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { runtimeEnv: { R_EXPLORE_MODEL: 'openrouter/anthropic/claude-haiku-4', }, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', }, }); @@ -461,7 +474,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { runtimeEnv: { R_MODEL_REASONING_EFFORT: 'xhigh', }, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', }, }); @@ -491,7 +504,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { runtimeEnv: { R_SMALL_MODEL_REASONING_EFFORT: 'nonsense', }, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', }, }); @@ -535,7 +548,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', }, }); @@ -559,7 +572,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', ANTHROPIC_API_KEY: 'sk-anthropic', OPENAI_API_KEY: 'sk-openai', @@ -594,7 +607,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { AWS_BEARER_TOKEN_BEDROCK: 'bedrock-key', AWS_REGION: 'us-west-2', GOOGLE_APPLICATION_CREDENTIALS: '{"type":"service_account"}', @@ -635,7 +648,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: {}, + modelProviderEnvVars: {}, }); expect(mockResolveOpenCodeAuthContent).toHaveBeenCalled(); @@ -655,7 +668,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: {}, + modelProviderEnvVars: {}, }); expect(env.R_CHATGPT_FAST_MODE).toBe('1'); @@ -673,7 +686,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveSandboxModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: {}, + modelProviderEnvVars: {}, }); // The OAuth record must stay on the control plane; the marker tells the @@ -690,7 +703,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveSandboxModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: {}, + modelProviderEnvVars: {}, }); expect(env).not.toHaveProperty('OPENCODE_AUTH_CONTENT'); @@ -716,7 +729,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveSandboxModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: {}, + modelProviderEnvVars: {}, }); expect(env.R_INFERENCE_GATEWAY_GITHUB_COPILOT).toBe('1'); @@ -737,7 +750,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveSandboxModelRuntimeEnv({ runtimeEnv: {}, // No XAI_API_KEY: subscription alone must cover the gateway path. - deploymentEnvVars: {}, + modelProviderEnvVars: {}, }); expect(mockGetFreshXaiAccessToken).toHaveBeenCalled(); @@ -760,7 +773,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveSandboxModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: {}, + modelProviderEnvVars: {}, }); expect(env).not.toHaveProperty('R_INFERENCE_GATEWAY_XAI'); @@ -780,7 +793,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: {}, + modelProviderEnvVars: {}, }); // OpenCode's xAI provider is API-key shaped: inject the access token as @@ -802,7 +815,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { XAI_API_KEY: 'sk-byok-key' }, + modelProviderEnvVars: { XAI_API_KEY: 'sk-byok-key' }, }); // Match gateway precedence: subscription wins when connected. @@ -816,7 +829,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { ANTHROPIC_API_KEY: 'sk-anthropic' }, + modelProviderEnvVars: { ANTHROPIC_API_KEY: 'sk-anthropic' }, }); expect(mockResolveOpenCodeAuthContent).not.toHaveBeenCalled(); @@ -831,7 +844,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: {}, + modelProviderEnvVars: {}, }); expect(env).not.toHaveProperty('OPENCODE_AUTH_CONTENT'); @@ -847,7 +860,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { it('withholds gateway-served keys and advertises them by name when enabled', async () => { const env = await resolveSandboxModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { ANTHROPIC_API_KEY: 'sk-anthropic' }, + modelProviderEnvVars: { ANTHROPIC_API_KEY: 'sk-anthropic' }, }); expect(env).not.toHaveProperty('ANTHROPIC_API_KEY'); @@ -860,7 +873,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { it('keeps raw keys for control-plane resolution', async () => { const env = await resolveEffectiveModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { ANTHROPIC_API_KEY: 'sk-anthropic' }, + modelProviderEnvVars: { ANTHROPIC_API_KEY: 'sk-anthropic' }, }); expect(env.ANTHROPIC_API_KEY).toBe('sk-anthropic'); @@ -874,7 +887,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { // for its own code survives into the sandbox. const env = await resolveSandboxModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { ANTHROPIC_API_KEY: 'sk-anthropic', OPENAI_API_KEY: 'sk-openai-for-user-code', }, @@ -924,7 +937,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { const env = await resolveSandboxModelRuntimeEnv({ runtimeEnv: {}, - deploymentEnvVars: { + modelProviderEnvVars: { OPENROUTER_API_KEY: 'sk-openrouter', ANTHROPIC_API_KEY: 'sk-anthropic', }, @@ -945,7 +958,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { R_MODEL_ENV_KEYS: 'ANTHROPIC_API_KEY,AWS_BEARER_TOKEN_BEDROCK,GOOGLE_APPLICATION_CREDENTIALS,MISTRAL_API_KEY', }, - deploymentEnvVars: { + modelProviderEnvVars: { ANTHROPIC_API_KEY: 'sk-anthropic', AWS_BEARER_TOKEN_BEDROCK: 'bedrock-key', GOOGLE_APPLICATION_CREDENTIALS: '{"type":"service_account"}', @@ -975,7 +988,7 @@ describe('resolveEffectiveModelRuntimeEnv', () => { LITELLM_BASE_URL: 'http://localhost:4000', LITELLM_API_KEY: 'litellm-key', }, - deploymentEnvVars: {}, + modelProviderEnvVars: {}, }); expect(env.R_MODEL).toBe('litellm/qwen3.6-35b-local'); diff --git a/packages/db/src/lib/model-runtime-config.ts b/packages/db/src/lib/model-runtime-config.ts index c059fee82..4078be5a7 100644 --- a/packages/db/src/lib/model-runtime-config.ts +++ b/packages/db/src/lib/model-runtime-config.ts @@ -32,10 +32,11 @@ import { getFreshXaiAccessToken } from './xai-subscription'; import { type DatabaseOrTransaction, db } from '../db'; import { deploymentSettings } from '../schema'; +import { stringifyDecryptedEnvVarValue } from './environment-variables'; import { - resolveDeploymentEnvVar, - stringifyDecryptedEnvVarValue, -} from './environment-variables'; + getPersistedModelProviderEnvironmentVariables, + getPersistedModelProviderEnvironmentVariableValues, +} from './model-provider-environment-variables'; const DEFAULT_DEPLOYMENT_ID = 'default'; const DISABLED_MODEL_PROVIDER_ENV_VAR_NAME_SET = new Set( @@ -149,8 +150,8 @@ function resolveProviderKeyNames({ /** * Resolve a single model-provider env value with the same precedence the task - * runtime uses: the runtime process env first, then the persisted (encrypted) - * deployment environment variables. + * runtime uses: the runtime process env first, then the dedicated persisted + * model-provider values. */ export async function resolveModelProviderEnvValue( envVarNames: string | readonly string[], @@ -170,13 +171,14 @@ export async function resolveModelProviderEnvValue( } } - for (const envVarName of names) { - const persistedValue = await resolveDeploymentEnvVar( - envVarName, + const persistedValues = + await getPersistedModelProviderEnvironmentVariableValues( + names, options.executor ?? db, - {}, ); + for (const envVarName of names) { + const persistedValue = persistedValues[envVarName]; const normalizedValue = normalizeConfiguredValue(persistedValue); if (normalizedValue) { @@ -189,7 +191,7 @@ export async function resolveModelProviderEnvValue( type ModelRuntimeEnvOptions = { runtimeEnv?: Partial>; - deploymentEnvVars?: Record; + modelProviderEnvVars?: Record; executor?: DatabaseOrTransaction; }; @@ -227,10 +229,8 @@ async function resolveModelRuntimeEnv( persistedEnvVars, { runtimeModelConfig, catalogModels, enabledCatalogModels }, ] = await Promise.all([ - resolveEffectiveDeploymentEnvVars({ - deploymentEnvVars: options.deploymentEnvVars, - executor, - }), + options.modelProviderEnvVars ?? + getPersistedModelProviderEnvironmentVariables(executor), loadPersistedRuntimeModelConfig(executor), ]); const persistedRuntimeModelConfig = runtimeModelConfig; diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 066db8249..995a2e48e 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -2872,6 +2872,48 @@ export const environmentVariablesRelations = relations( }), ); +/** + * model_provider_environment_variables + * + * Model-provider credentials and configuration persisted by the Models + * settings flow. Matching rows remain in environment_variables during the + * N-1 compatibility release so the previous application version can roll + * back safely. + */ +export const modelProviderEnvironmentVariables = pgTable( + 'model_provider_environment_variables', + { + id: uuid('id').primaryKey().defaultRandom(), + name: text('name').notNull(), + value: encryptedJson('value').notNull(), + createdByUserId: text('created_by_user_id').references(() => users.id), + lastUpdatedByUserId: text('last_updated_by_user_id').references( + () => users.id, + ), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at').notNull().defaultNow(), + }, + (table) => [ + uniqueIndex('model_provider_environment_variables_name_unique').on( + table.name, + ), + ], +); + +export const modelProviderEnvironmentVariablesRelations = relations( + modelProviderEnvironmentVariables, + ({ one }) => ({ + createdByUser: one(users, { + fields: [modelProviderEnvironmentVariables.createdByUserId], + references: [users.id], + }), + lastUpdatedByUser: one(users, { + fields: [modelProviderEnvironmentVariables.lastUpdatedByUserId], + references: [users.id], + }), + }), +); + /** * deployment_secrets * diff --git a/packages/db/src/server.ts b/packages/db/src/server.ts index fcefd4ccd..9a589741c 100644 --- a/packages/db/src/server.ts +++ b/packages/db/src/server.ts @@ -41,6 +41,7 @@ export * from './lib/map-raw-row'; export * from './lib/legacy-task-inference-usage'; export * from './lib/deployment-auth-keypairs'; export * from './lib/environment-variables'; +export * from './lib/model-provider-environment-variables'; export * from './lib/task-id'; export * from './lib/task-activity-timestamp'; export * from './lib/acting-user'; @@ -169,6 +170,8 @@ export { trackedMessagesRelations, environmentVariables, environmentVariablesRelations, + modelProviderEnvironmentVariables, + modelProviderEnvironmentVariablesRelations, deploymentSecrets, webhooks, environments, diff --git a/packages/db/src/types.ts b/packages/db/src/types.ts index 84db91281..740560bad 100644 --- a/packages/db/src/types.ts +++ b/packages/db/src/types.ts @@ -48,6 +48,7 @@ import type { slackQuickAnswers, linearPendingSelections, environmentVariables, + modelProviderEnvironmentVariables, environments, environmentConfigVersions, environmentRepositoryMappings, @@ -379,6 +380,18 @@ export type CreateEnvironmentVariable = Omit< Generated >; +/** + * modelProviderEnvironmentVariables + */ + +export type ModelProviderEnvironmentVariable = + typeof modelProviderEnvironmentVariables.$inferSelect; + +export type CreateModelProviderEnvironmentVariable = Omit< + typeof modelProviderEnvironmentVariables.$inferInsert, + Generated +>; + /** * environments */ diff --git a/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-helpers.test.ts b/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-helpers.test.ts index 341fc57f5..a0619ecc7 100644 --- a/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-helpers.test.ts +++ b/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-helpers.test.ts @@ -104,6 +104,7 @@ import { resolveWorkspaceSourceControlProvider } from '@roomote/db/server'; import { createSourceControlTokenForTaskRun, fetchResolvedRuntimeEnvVars, + flattenResolvedRuntimeEnvVars, notifyCanceledTaskRunOnSettle, redactControlPlaneEnvVars, redactSourceControlProviderEnvVars, @@ -585,14 +586,19 @@ describe('fetchResolvedRuntimeEnvVars', () => { MY_APP_CONFIG: 'value', }); - expect(envVars).toMatchObject({ + expect(mockResolveSandboxModelRuntimeEnv).toHaveBeenCalledWith(); + + expect(envVars.envVars).toEqual({ MY_APP_CONFIG: 'value' }); + expect(envVars.modelRuntimeEnv).toMatchObject({ R_MODEL: 'anthropic/claude-test', - ROOMOTE_MODEL: 'anthropic/claude-test', R_MODEL_REASONING_EFFORT: 'high', - ROOMOTE_MODEL_REASONING_EFFORT: 'high', R_MODEL_ENV_KEYS: 'ANTHROPIC_API_KEY', + ANTHROPIC_API_KEY: 'sk-ant', + }); + expect(flattenResolvedRuntimeEnvVars(envVars)).toMatchObject({ + ROOMOTE_MODEL: 'anthropic/claude-test', + ROOMOTE_MODEL_REASONING_EFFORT: 'high', ROOMOTE_MODEL_ENV_KEYS: 'ANTHROPIC_API_KEY', - MY_APP_CONFIG: 'value', }); }); @@ -605,8 +611,8 @@ describe('fetchResolvedRuntimeEnvVars', () => { ROOMOTE_MODEL: 'operator/explicit', }); - expect(envVars.ROOMOTE_MODEL).toBe('anthropic/claude-test'); - expect(envVars.R_MODEL).toBe('anthropic/claude-test'); + expect(envVars.envVars).not.toHaveProperty('ROOMOTE_MODEL'); + expect(envVars.modelRuntimeEnv.R_MODEL).toBe('anthropic/claude-test'); }); it('admits no raw provider keys when the gateway is enabled', async () => { @@ -621,10 +627,13 @@ describe('fetchResolvedRuntimeEnvVars', () => { MY_APP_CONFIG: 'value', }); - expect(envVars).not.toHaveProperty('ANTHROPIC_API_KEY'); - expect(envVars).not.toHaveProperty('OPENAI_API_KEY'); - expect(envVars.MY_APP_CONFIG).toBe('value'); - expect(envVars.R_INFERENCE_GATEWAY_KEYS).toBe('ANTHROPIC_API_KEY'); + expect(mockResolveSandboxModelRuntimeEnv).toHaveBeenCalledWith(); + expect(envVars.envVars).not.toHaveProperty('ANTHROPIC_API_KEY'); + expect(envVars.envVars).not.toHaveProperty('OPENAI_API_KEY'); + expect(envVars.envVars.MY_APP_CONFIG).toBe('value'); + expect(envVars.modelRuntimeEnv.R_INFERENCE_GATEWAY_KEYS).toBe( + 'ANTHROPIC_API_KEY', + ); }); it('admits only resolver-selected provider keys when the resolver returns raw keys', async () => { @@ -641,12 +650,14 @@ describe('fetchResolvedRuntimeEnvVars', () => { STRIPE_API_KEY: 'sk-stripe', }); - expect(envVars.ANTHROPIC_API_KEY).toBe('sk-ant'); - expect(envVars).not.toHaveProperty('OPENAI_API_KEY'); - expect(envVars).not.toHaveProperty('AWS_BEARER_TOKEN_BEDROCK'); - expect(envVars.AWS_REGION).toBe('us-west-2'); - expect(envVars.STRIPE_API_KEY).toBe('sk-stripe'); - expect(envVars).not.toHaveProperty('R_INFERENCE_GATEWAY_KEYS'); + expect(envVars.modelRuntimeEnv.ANTHROPIC_API_KEY).toBe('sk-ant'); + expect(envVars.envVars).not.toHaveProperty('OPENAI_API_KEY'); + expect(envVars.envVars).not.toHaveProperty('AWS_BEARER_TOKEN_BEDROCK'); + expect(envVars.envVars.AWS_REGION).toBe('us-west-2'); + expect(envVars.envVars.STRIPE_API_KEY).toBe('sk-stripe'); + expect(envVars.modelRuntimeEnv).not.toHaveProperty( + 'R_INFERENCE_GATEWAY_KEYS', + ); }); it('treats custom R_MODEL_ENV_KEYS credentials as resolver-managed', async () => { @@ -663,8 +674,8 @@ describe('fetchResolvedRuntimeEnvVars', () => { MY_APP_CONFIG: 'value', }); - expect(envVars.CUSTOM_LLM_TOKEN).toBe('selected-token'); - expect(envVars).not.toHaveProperty('STALE_LLM_TOKEN'); - expect(envVars.MY_APP_CONFIG).toBe('value'); + expect(envVars.modelRuntimeEnv.CUSTOM_LLM_TOKEN).toBe('selected-token'); + expect(envVars.envVars).not.toHaveProperty('STALE_LLM_TOKEN'); + expect(envVars.envVars.MY_APP_CONFIG).toBe('value'); }); }); diff --git a/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-resume-task-run.test.ts b/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-resume-task-run.test.ts index d7290f758..0b48fb4df 100644 --- a/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-resume-task-run.test.ts +++ b/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-resume-task-run.test.ts @@ -78,6 +78,10 @@ vi.mock('../dequeue-helpers', () => ({ fetchEnvVars: (...args: unknown[]) => mockFetchEnvVars(...args), fetchResolvedRuntimeEnvVars: (...args: unknown[]) => mockFetchResolvedRuntimeEnvVars(...args), + flattenResolvedRuntimeEnvVars: (resolved: { + envVars: Record; + modelRuntimeEnv: Record; + }) => ({ ...resolved.envVars, ...resolved.modelRuntimeEnv }), cancelAndReleaseTaskRun: (...args: unknown[]) => mockCancelAndReleaseTaskRun(...args), notifyCanceledTaskRunOnSettle: (...args: unknown[]) => @@ -161,7 +165,10 @@ describe('dequeueResumeTaskRun', () => { source: 'app', expiresAt: null, }); - mockFetchResolvedRuntimeEnvVars.mockResolvedValue({ RESOLVED_ENV: '1' }); + mockFetchResolvedRuntimeEnvVars.mockResolvedValue({ + envVars: { RESOLVED_ENV: '1' }, + modelRuntimeEnv: {}, + }); mockCancelTaskRun.mockResolvedValue(undefined); mockCancelAndReleaseTaskRun.mockResolvedValue(undefined); mockReleaseTaskRun.mockResolvedValue(true); diff --git a/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-task-run.test.ts b/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-task-run.test.ts index 5f8849341..93b492dcb 100644 --- a/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-task-run.test.ts +++ b/packages/sdk/src/server/lib/task-runs/__tests__/dequeue-task-run.test.ts @@ -88,6 +88,10 @@ vi.mock('../dequeue-helpers', () => ({ fetchEnvVars: (...args: unknown[]) => mockFetchEnvVars(...args), fetchResolvedRuntimeEnvVars: (...args: unknown[]) => mockFetchResolvedRuntimeEnvVars(...args), + flattenResolvedRuntimeEnvVars: (resolved: { + envVars: Record; + modelRuntimeEnv: Record; + }) => ({ ...resolved.envVars, ...resolved.modelRuntimeEnv }), claimJobById: (...args: unknown[]) => mockClaimJobById(...args), cancelAndReleaseTaskRun: (...args: unknown[]) => mockCancelAndReleaseTaskRun(...args), @@ -229,7 +233,10 @@ describe('dequeueTaskRun', () => { source: 'app', expiresAt: null, }); - mockFetchResolvedRuntimeEnvVars.mockResolvedValue({ RESOLVED_ENV: '1' }); + mockFetchResolvedRuntimeEnvVars.mockResolvedValue({ + envVars: { RESOLVED_ENV: '1' }, + modelRuntimeEnv: {}, + }); mockCancelTaskRun.mockResolvedValue(undefined); mockCancelAndReleaseTaskRun.mockResolvedValue(undefined); mockReleaseTaskRun.mockResolvedValue(true); @@ -342,6 +349,27 @@ describe('dequeueTaskRun', () => { ); }); + it('returns generic and model runtime env separately to v2 workers', async () => { + const taskRun = makeStandardTaskRun(); + mockTxExecute.mockResolvedValue([{ id: taskRun.id }]); + mockTxFindFirstTaskRuns.mockResolvedValue(taskRun); + mockFetchResolvedRuntimeEnvVars.mockResolvedValue({ + envVars: { MY_APP_CONFIG: 'value' }, + modelRuntimeEnv: { ANTHROPIC_API_KEY: 'model-secret' }, + }); + + const result = await dequeueTaskRun({ orgId: 'org-1' } as never, { + runId: taskRun.id, + envContractVersion: 2, + }); + + expect(result?.envVars).toMatchObject({ MY_APP_CONFIG: 'value' }); + expect(result?.envVars).not.toHaveProperty('ANTHROPIC_API_KEY'); + expect(result?.modelRuntimeEnv).toEqual({ + ANTHROPIC_API_KEY: 'model-secret', + }); + }); + it('persists worker runtime metadata when the worker claims the run', async () => { const taskRun = makeStandardTaskRun(); diff --git a/packages/sdk/src/server/lib/task-runs/__tests__/fetch-snapshot-env.test.ts b/packages/sdk/src/server/lib/task-runs/__tests__/fetch-snapshot-env.test.ts index b48f67639..9386edab4 100644 --- a/packages/sdk/src/server/lib/task-runs/__tests__/fetch-snapshot-env.test.ts +++ b/packages/sdk/src/server/lib/task-runs/__tests__/fetch-snapshot-env.test.ts @@ -30,6 +30,10 @@ vi.mock('@roomote/db/server', () => ({ vi.mock('../dequeue-helpers', () => ({ fetchResolvedRuntimeEnvVars: mockFetchResolvedRuntimeEnvVars, + flattenResolvedRuntimeEnvVars: (resolved: { + envVars: Record; + modelRuntimeEnv: Record; + }) => ({ ...resolved.envVars, ...resolved.modelRuntimeEnv }), createSourceControlTokenForTaskRun: mockCreateSourceControlTokenForTaskRun, })); @@ -76,15 +80,20 @@ describe('fetchSnapshotEnv', () => { const taskRun = makeTaskRun(); mockFindFirst.mockResolvedValue(taskRun); mockFetchResolvedRuntimeEnvVars.mockResolvedValue({ - MY_SECRET: 'value123', + envVars: { MY_SECRET: 'value123' }, + modelRuntimeEnv: { ANTHROPIC_API_KEY: 'model-secret' }, }); const token = makeGitHubToken('ghs_token_abc'); mockCreateSourceControlTokenForTaskRun.mockResolvedValue(token); - const result = await fetchSnapshotEnv(auth, { runId: 42 }); + const result = await fetchSnapshotEnv(auth, { + runId: 42, + envContractVersion: 2, + }); expect(result).toEqual({ envVars: { MY_SECRET: 'value123' }, + modelRuntimeEnv: { ANTHROPIC_API_KEY: 'model-secret' }, gitHubToken: 'ghs_token_abc', sourceControlToken: token, taskId: 'task_123', @@ -119,7 +128,10 @@ describe('fetchSnapshotEnv', () => { const taskRun = makeTaskRun(); mockFindFirst.mockResolvedValue(taskRun); - mockFetchResolvedRuntimeEnvVars.mockResolvedValue({}); + mockFetchResolvedRuntimeEnvVars.mockResolvedValue({ + envVars: {}, + modelRuntimeEnv: {}, + }); const token = makeGitHubToken('ghs_job_token'); mockCreateSourceControlTokenForTaskRun.mockResolvedValue(token); @@ -167,7 +179,10 @@ describe('fetchSnapshotEnv', () => { }; mockFindFirst.mockResolvedValue(makeTaskRun()); - mockFetchResolvedRuntimeEnvVars.mockResolvedValue({}); + mockFetchResolvedRuntimeEnvVars.mockResolvedValue({ + envVars: {}, + modelRuntimeEnv: {}, + }); mockCreateSourceControlTokenForTaskRun.mockResolvedValue( makeGitHubToken('ghs_token_xyz'), ); @@ -189,7 +204,10 @@ describe('fetchSnapshotEnv', () => { }; mockFindFirst.mockResolvedValue(makeTaskRun()); - mockFetchResolvedRuntimeEnvVars.mockResolvedValue({ KEY: 'val' }); + mockFetchResolvedRuntimeEnvVars.mockResolvedValue({ + envVars: { KEY: 'val' }, + modelRuntimeEnv: {}, + }); mockCreateSourceControlTokenForTaskRun.mockResolvedValue(null); await expect(fetchSnapshotEnv(auth, { runId: 42 })).rejects.toThrow( diff --git a/packages/sdk/src/server/lib/task-runs/dequeue-helpers.ts b/packages/sdk/src/server/lib/task-runs/dequeue-helpers.ts index a0a8f8f20..28f84fa9d 100644 --- a/packages/sdk/src/server/lib/task-runs/dequeue-helpers.ts +++ b/packages/sdk/src/server/lib/task-runs/dequeue-helpers.ts @@ -245,22 +245,39 @@ export async function fetchResolvedRuntimeEnvVars( options?: { sourceControlProvider?: SourceControlProvider; }, -): Promise> { +): Promise<{ + envVars: Record; + modelRuntimeEnv: Record; +}> { const envVars = deploymentEnvVars ?? (await loadPersistedDeploymentEnvVarsFromDb()); - const resolvedModelRuntimeEnv = await resolveSandboxModelRuntimeEnv({ - deploymentEnvVars: envVars, - }); + // Model-provider credentials resolve from their dedicated store. Generic + // task variables must never satisfy or override provider authentication. + const resolvedModelRuntimeEnv = await resolveSandboxModelRuntimeEnv(); + + return { + envVars: redactControlPlaneEnvVars( + redactSourceControlProviderEnvVars( + redactModelRuntimeManagedEnvVars(envVars, resolvedModelRuntimeEnv), + options?.sourceControlProvider, + ), + ), + modelRuntimeEnv: redactControlPlaneEnvVars( + redactInferenceGatewayProviderKeys(resolvedModelRuntimeEnv), + ), + }; +} +export function flattenResolvedRuntimeEnvVars(input: { + envVars: Record; + modelRuntimeEnv: Record; +}): Record { return redactControlPlaneEnvVars( - redactSourceControlProviderEnvVars( - redactInferenceGatewayProviderKeys( - withLegacySnapshotModelEnvAliases({ - ...redactModelRuntimeManagedEnvVars(envVars, resolvedModelRuntimeEnv), - ...resolvedModelRuntimeEnv, - }), - ), - options?.sourceControlProvider, + redactInferenceGatewayProviderKeys( + withLegacySnapshotModelEnvAliases({ + ...input.envVars, + ...input.modelRuntimeEnv, + }), ), ); } diff --git a/packages/sdk/src/server/lib/task-runs/dequeue-resume-task-run.ts b/packages/sdk/src/server/lib/task-runs/dequeue-resume-task-run.ts index 42432b6ae..54364ed55 100644 --- a/packages/sdk/src/server/lib/task-runs/dequeue-resume-task-run.ts +++ b/packages/sdk/src/server/lib/task-runs/dequeue-resume-task-run.ts @@ -25,6 +25,7 @@ import { claimJobById, fetchEnvVars, fetchResolvedRuntimeEnvVars, + flattenResolvedRuntimeEnvVars, cancelAndReleaseTaskRun, createSourceControlTokenForTaskRun, type SourceControlRuntimeToken, @@ -48,6 +49,7 @@ type DequeueResumeTaskRunResult = gitHubToken: string; sourceControlToken: SourceControlRuntimeToken; envVars: Record; + modelRuntimeEnv?: Record; harnessInstructions?: string; orgAgentInstructions?: string; setupOnboardingTask: boolean; @@ -98,6 +100,7 @@ export const dequeueResumeTaskRun = async ( workerReleaseTag?: string; workerVersion?: string; workerCommit?: string; + envContractVersion?: number; }, { onBootstrapFailure, @@ -413,10 +416,10 @@ export const dequeueResumeTaskRun = async ( const gitHubToken = sourceControlToken.provider === 'github' ? sourceControlToken.token : ''; - let resolvedEnvVars: Record; + let resolvedEnv: Awaited>; try { - resolvedEnvVars = await fetchResolvedRuntimeEnvVars(result.envVars, { + resolvedEnv = await fetchResolvedRuntimeEnvVars(result.envVars, { sourceControlProvider: sourceControlToken.provider, }); } catch (error) { @@ -444,7 +447,15 @@ export const dequeueResumeTaskRun = async ( return undefined; } - result.envVars = { ...resolvedEnvVars, ...sourceControlToken.envVars }; + result.envVars = { + ...(input.envContractVersion === 2 + ? resolvedEnv.envVars + : flattenResolvedRuntimeEnvVars(resolvedEnv)), + ...sourceControlToken.envVars, + }; + if (input.envContractVersion === 2) { + Object.assign(result, { modelRuntimeEnv: resolvedEnv.modelRuntimeEnv }); + } await recordSnapshotResumeBootstrapEvent({ runId: result.taskRun.id, diff --git a/packages/sdk/src/server/lib/task-runs/dequeue-task-run.ts b/packages/sdk/src/server/lib/task-runs/dequeue-task-run.ts index ac5ea054e..8a25a8f80 100644 --- a/packages/sdk/src/server/lib/task-runs/dequeue-task-run.ts +++ b/packages/sdk/src/server/lib/task-runs/dequeue-task-run.ts @@ -24,6 +24,7 @@ import { type GitAuthor, fetchEnvVars, fetchResolvedRuntimeEnvVars, + flattenResolvedRuntimeEnvVars, cancelAndReleaseTaskRun, createSourceControlTokenForTaskRun, type SourceControlRuntimeToken, @@ -82,6 +83,7 @@ type DequeueResult = gitHubToken: string; sourceControlToken: SourceControlRuntimeToken; envVars: Record; + modelRuntimeEnv?: Record; orgAgentInstructions?: string; setupOnboardingTask: boolean; gitAuthor: GitAuthor; @@ -258,6 +260,7 @@ export const dequeueTaskRun = async ( workerReleaseTag?: string; workerVersion?: string; workerCommit?: string; + envContractVersion?: number; }, { onBootstrapFailure, @@ -266,7 +269,13 @@ export const dequeueTaskRun = async ( } = {}, ) => { try { - const { runId, workerReleaseTag, workerVersion, workerCommit } = input; + const { + runId, + workerReleaseTag, + workerVersion, + workerCommit, + envContractVersion, + } = input; const query = claimJobById(runId); const tag = '[dequeueTaskRun]'; @@ -518,10 +527,10 @@ export const dequeueTaskRun = async ( } } - let resolvedEnvVars: Record; + let resolvedEnv: Awaited>; try { - resolvedEnvVars = await recordBootstrapPhase({ + resolvedEnv = await recordBootstrapPhase({ runId: txResult.taskRun.id, taskId: txResult.taskRun.taskId, label: 'resolveRuntimeEnvVars', @@ -561,9 +570,14 @@ export const dequeueTaskRun = async ( gitHubToken, sourceControlToken, envVars: { - ...resolvedEnvVars, + ...(envContractVersion === 2 + ? resolvedEnv.envVars + : flattenResolvedRuntimeEnvVars(resolvedEnv)), ...sourceControlToken.envVars, }, + ...(envContractVersion === 2 && { + modelRuntimeEnv: resolvedEnv.modelRuntimeEnv, + }), orgAgentInstructions: txResult.orgAgentInstructions, setupOnboardingTask: slackTaskRunRouting.route.kind === 'setup-onboarding', diff --git a/packages/sdk/src/server/lib/task-runs/fetch-snapshot-env.ts b/packages/sdk/src/server/lib/task-runs/fetch-snapshot-env.ts index f2c6186df..bb944b82e 100644 --- a/packages/sdk/src/server/lib/task-runs/fetch-snapshot-env.ts +++ b/packages/sdk/src/server/lib/task-runs/fetch-snapshot-env.ts @@ -8,6 +8,7 @@ import { db, taskRuns, eq } from '@roomote/db/server'; import { fetchResolvedRuntimeEnvVars, + flattenResolvedRuntimeEnvVars, createSourceControlTokenForTaskRun, } from './dequeue-helpers'; @@ -18,9 +19,10 @@ import { */ export async function fetchSnapshotEnv( _auth: AuthTokenContext | RunTokenContext, - input: { runId: number }, + input: { runId: number; envContractVersion?: number }, ): Promise<{ envVars: Record; + modelRuntimeEnv?: Record; gitHubToken: string; sourceControlToken: SourceControlTokenMetadata; taskId: string; @@ -40,7 +42,7 @@ export async function fetchSnapshotEnv( // dequeue so gateway-covered provider keys are withheld here too; otherwise // a snapshot taken with the flag on would bake raw provider keys into the // snapshot's shell env and the persisted image. - const envVars = await fetchResolvedRuntimeEnvVars(undefined, { + const resolvedEnv = await fetchResolvedRuntimeEnvVars(undefined, { sourceControlProvider: resolveSourceControlProviderFromPayload( taskRun.payload, ), @@ -79,5 +81,16 @@ export async function fetchSnapshotEnv( const gitHubToken = sourceControlToken.provider === 'github' ? sourceControlToken.token : ''; - return { envVars, gitHubToken, sourceControlToken, taskId: taskRun.taskId }; + return { + envVars: + input.envContractVersion === 2 + ? resolvedEnv.envVars + : flattenResolvedRuntimeEnvVars(resolvedEnv), + ...(input.envContractVersion === 2 && { + modelRuntimeEnv: resolvedEnv.modelRuntimeEnv, + }), + gitHubToken, + sourceControlToken, + taskId: taskRun.taskId, + }; } diff --git a/packages/sdk/src/server/lib/task-runs/get-resolved-runtime-env-vars.ts b/packages/sdk/src/server/lib/task-runs/get-resolved-runtime-env-vars.ts index 932acf501..4cbd56b53 100644 --- a/packages/sdk/src/server/lib/task-runs/get-resolved-runtime-env-vars.ts +++ b/packages/sdk/src/server/lib/task-runs/get-resolved-runtime-env-vars.ts @@ -5,11 +5,14 @@ import { } from '@roomote/types'; import { db, eq, taskRuns } from '@roomote/db/server'; -import { fetchResolvedRuntimeEnvVars } from './dequeue-helpers'; +import { + fetchResolvedRuntimeEnvVars, + flattenResolvedRuntimeEnvVars, +} from './dequeue-helpers'; export async function getResolvedRuntimeEnvVars( _auth: AuthTokenContext | RunTokenContext, - input: { runId: number }, + input: { runId: number; envContractVersion?: number }, ) { const taskRun = await db.query.taskRuns.findFirst({ where: eq(taskRuns.id, input.runId), @@ -20,9 +23,13 @@ export async function getResolvedRuntimeEnvVars( throw new Error('Task run not found'); } - return fetchResolvedRuntimeEnvVars(undefined, { + const resolvedEnv = await fetchResolvedRuntimeEnvVars(undefined, { sourceControlProvider: resolveSourceControlProviderFromPayload( taskRun.payload, ), }); + + return input.envContractVersion === 2 + ? resolvedEnv + : flattenResolvedRuntimeEnvVars(resolvedEnv); } diff --git a/packages/sdk/src/server/routers/task-runs.ts b/packages/sdk/src/server/routers/task-runs.ts index dae3efb45..475376603 100644 --- a/packages/sdk/src/server/routers/task-runs.ts +++ b/packages/sdk/src/server/routers/task-runs.ts @@ -242,6 +242,7 @@ const workerReleaseMetadataSchema = z.object({ workerReleaseTag: z.string().optional(), workerVersion: z.string().optional(), workerCommit: z.string().optional(), + envContractVersion: z.number().int().optional(), }); function runTokenOnlyScoped( @@ -1012,11 +1013,18 @@ export const taskRunsRouter = router({ timestamp: input.timestamp, }), ), - fetchSnapshotEnv: runScoped(z.object({ runId: z.number() }), 'runId').query( - ({ ctx, input }) => fetchSnapshotEnv(ctx.auth, input), - ), + fetchSnapshotEnv: runScoped( + z.object({ + runId: z.number(), + envContractVersion: z.number().int().optional(), + }), + 'runId', + ).query(({ ctx, input }) => fetchSnapshotEnv(ctx.auth, input)), getResolvedRuntimeEnvVars: runScoped( - z.object({ runId: z.number() }), + z.object({ + runId: z.number(), + envContractVersion: z.number().int().optional(), + }), 'runId', ).query(({ ctx, input }) => getResolvedRuntimeEnvVars(ctx.auth, input)),