From b2ee3403c7b4ddc136453ee37c1d49672fd46b28 Mon Sep 17 00:00:00 2001
From: Matt Rubens <2600+mrubens@users.noreply.github.com>
Date: Mon, 3 Aug 2026 04:08:33 +0000
Subject: [PATCH 1/2] feat: separate model credentials from task environment
---
apps/web/src/components/settings/EnvVars.tsx | 3 +-
.../environment-variables/index.test.ts | 46 +-
.../commands/environment-variables/index.ts | 157 +-
...del-provider-environment-variables.test.ts | 65 +
.../src/trpc/commands/setup-new/index.test.ts | 7 +
apps/web/src/trpc/commands/setup-new/index.ts | 26 +-
.../trpc/commands/task-models/index.test.ts | 36 +-
.../src/trpc/commands/task-models/index.ts | 55 +-
.../local-provider-discovery.test.ts | 4 +-
.../task-models/local-provider-discovery.ts | 4 +-
apps/worker/src/commands/snapshot.ts | 2 +-
.../commands/utils/execute-task-run.test.ts | 33 +-
.../src/commands/utils/execute-task-run.ts | 19 +-
apps/worker/src/env/worker-env.ts | 16 +
.../src/monitoring/worker-release-metadata.ts | 2 +
.../src/run-task/__tests__/run-task.test.ts | 7 +-
apps/worker/src/run-task/env.ts | 14 +
apps/worker/src/run-task/run-task.ts | 19 +-
apps/worker/src/run-task/types.ts | 2 +
.../opencode-server-bootstrap.test.ts | 35 +
.../harnesses/opencode-server/bootstrap.ts | 33 +-
.../__tests__/reloadDeploymentEnvVars.test.ts | 52 +-
.../procedures/reloadDeploymentEnvVars.ts | 37 +-
packages/db/drizzle/0028_odd_wolfsbane.sql | 71 +
packages/db/drizzle/meta/0028_snapshot.json | 10020 ++++++++++++++++
packages/db/drizzle/meta/_journal.json | 7 +
.../__tests__/model-runtime-config.test.ts | 39 +-
...del-provider-environment-variables.test.ts | 117 +
.../model-provider-environment-variables.ts | 162 +
.../db/src/lib/model-runtime-config.test.ts | 97 +-
packages/db/src/lib/model-runtime-config.ts | 28 +-
packages/db/src/schema.ts | 42 +
packages/db/src/server.ts | 3 +
packages/db/src/types.ts | 13 +
.../__tests__/dequeue-helpers.test.ts | 49 +-
.../__tests__/dequeue-resume-task-run.test.ts | 9 +-
.../__tests__/dequeue-task-run.test.ts | 30 +-
.../__tests__/fetch-snapshot-env.test.ts | 28 +-
.../server/lib/task-runs/dequeue-helpers.ts | 41 +-
.../lib/task-runs/dequeue-resume-task-run.ts | 17 +-
.../server/lib/task-runs/dequeue-task-run.ts | 22 +-
.../lib/task-runs/fetch-snapshot-env.ts | 19 +-
.../get-resolved-runtime-env-vars.ts | 13 +-
packages/sdk/src/server/routers/task-runs.ts | 16 +-
44 files changed, 11283 insertions(+), 234 deletions(-)
create mode 100644 apps/web/src/trpc/commands/environment-variables/model-provider-environment-variables.test.ts
create mode 100644 packages/db/drizzle/0028_odd_wolfsbane.sql
create mode 100644 packages/db/drizzle/meta/0028_snapshot.json
create mode 100644 packages/db/src/lib/model-provider-environment-variables.test.ts
create mode 100644 packages/db/src/lib/model-provider-environment-variables.ts
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__/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/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/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..bcec5453e 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,6 +71,7 @@ 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 ctx = {
@@ -107,8 +109,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(
@@ -125,19 +130,16 @@ describe('reloadDeploymentEnvVars procedure', () => {
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 +152,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,6 +176,7 @@ 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',
@@ -181,6 +194,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..8c253fc15 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,22 @@ function omitKeys(
return nextEnv;
}
+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 +53,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 +65,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,16 +81,18 @@ 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,
});
return {
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)),
From e0f9cd44ae3f2b4405a589cf57e749742339dee7 Mon Sep 17 00:00:00 2001
From: Matt Rubens <2600+mrubens@users.noreply.github.com>
Date: Mon, 3 Aug 2026 04:18:47 +0000
Subject: [PATCH 2/2] fix: reconnect harness after model credential reload
---
.../run-task/__tests__/create-harness.test.ts | 6 ++++-
apps/worker/src/run-task/create-harness.ts | 5 +++-
.../src/run-task/reconnectable-harness.ts | 6 +++++
.../__tests__/reloadDeploymentEnvVars.test.ts | 13 ++++++++--
.../procedures/reloadDeploymentEnvVars.ts | 25 +++++++++++++++++++
5 files changed, 51 insertions(+), 4 deletions(-)
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/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/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/sandbox-server/procedures/__tests__/reloadDeploymentEnvVars.test.ts b/apps/worker/src/sandbox-server/procedures/__tests__/reloadDeploymentEnvVars.test.ts
index bcec5453e..4a1c2d421 100644
--- a/apps/worker/src/sandbox-server/procedures/__tests__/reloadDeploymentEnvVars.test.ts
+++ b/apps/worker/src/sandbox-server/procedures/__tests__/reloadDeploymentEnvVars.test.ts
@@ -74,12 +74,14 @@ function createCaller(workerEnv?: WorkerEnv, runId = 1) {
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: () => ({
@@ -101,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', () => {
@@ -125,7 +131,7 @@ 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();
@@ -182,6 +188,9 @@ describe('reloadDeploymentEnvVars procedure', () => {
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 () => {
diff --git a/apps/worker/src/sandbox-server/procedures/reloadDeploymentEnvVars.ts b/apps/worker/src/sandbox-server/procedures/reloadDeploymentEnvVars.ts
index 8c253fc15..3c882967c 100644
--- a/apps/worker/src/sandbox-server/procedures/reloadDeploymentEnvVars.ts
+++ b/apps/worker/src/sandbox-server/procedures/reloadDeploymentEnvVars.ts
@@ -23,6 +23,18 @@ 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;
@@ -95,6 +107,19 @@ export async function applyDeploymentEnvVarsReload(input: {
...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),