mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-22 00:30:19 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Connections is the subsystem that lets operators connect external apps and govern which subjects may use those credentials > - #9958 established the v3 schema foundation and #9981 adds the AppDefinition catalog layer > - The runtime still needs subject-aware authorization state, scoped key handling, and API/OpenAPI routes so connected apps can actually be granted and used safely > - This pull request adds the runtime grants/authorization behavior on top of the catalog branch, while keeping unrelated dependency and workflow sync commits out of the stack > - The benefit is a reviewable runtime layer that can land after the catalog PR, then unblock the wizard and orchestrator cutover work ## Linked Issues or Issue Description Refs #9958 and #9981. Refs #9981. No public GitHub issue exists for this branch. This is the runtime layer for the Connections v3 stack and is rebased onto `master` after #9981 landed. ## What Changed - Adds the connection user authorization state migration and schema wiring. - Adds shared runtime subject/grant types and validators. - Adds runtime grant and scoped key behavior in the tool-access service. - Adds runtime route coverage and registers the routes in OpenAPI. - Replays only the Connections runtime commits on top of the catalog branch, dropping unrelated sync/dependency history from the prior closed runtime PR. ## Verification - `pnpm run preflight:workspace-links` - `pnpm exec vitest run packages/shared/src/validators/tool-access.test.ts server/src/__tests__/tool-access-service.test.ts` ## Risks - Medium: runtime grant enforcement is security-sensitive and must fail closed for unknown key scopes. - Migration ordering depends on the schema and catalog layers already merged through #9958 and #9981. - This PR is rebased and retargeted to `master` with runtime-only commits. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI GPT-5 Codex coding agent with repository tool use. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
871 lines
47 KiB
TypeScript
871 lines
47 KiB
TypeScript
import {
|
|
sql,
|
|
} from "drizzle-orm";
|
|
import {
|
|
boolean,
|
|
check,
|
|
foreignKey,
|
|
index,
|
|
integer,
|
|
jsonb,
|
|
pgTable,
|
|
text,
|
|
timestamp,
|
|
unique,
|
|
uniqueIndex,
|
|
uuid,
|
|
} from "drizzle-orm/pg-core";
|
|
import type {
|
|
ConnectionTokenIssuanceOutcome,
|
|
ConnectionTokenIssuancePath,
|
|
McpConnectionCredentialRef,
|
|
ToolActionRequestStatus,
|
|
ToolApplicationStatus,
|
|
ToolApplicationType,
|
|
ToolAuditEventType,
|
|
ToolAuditOutcome,
|
|
ToolCatalogEntryKind,
|
|
ToolCatalogEntryStatus,
|
|
ToolConnectionHealthStatus,
|
|
ToolConnectionAuthKind,
|
|
ToolConnectionKind,
|
|
ToolConnectionOwnership,
|
|
ToolConnectionInstallTargetType,
|
|
ToolConnectionStatus,
|
|
ToolConnectionTransport,
|
|
ConnectionGrantKind,
|
|
ConnectionGrantStatus,
|
|
ToolCredentialSecretRef,
|
|
ToolInvocationApprovalState,
|
|
ToolInvocationStatus,
|
|
ToolMcpGatewayAuthConfig,
|
|
ToolMcpGatewayContextScopeType,
|
|
ToolMcpGatewayDefaultProfileMode,
|
|
ToolMcpGatewayHeaderPolicy,
|
|
ToolMcpGatewayMetadataPolicy,
|
|
ToolMcpGatewayOnDemandToolsConfig,
|
|
ToolMcpGatewayStatus,
|
|
ToolMcpGatewayTokenAction,
|
|
ToolMcpGatewayTokenSubjectType,
|
|
ToolPolicyDecision,
|
|
ToolPolicyType,
|
|
ToolProfileBindingTargetType,
|
|
ToolProfileDefaultAction,
|
|
ToolProfileEntryEffect,
|
|
ToolProfileEntrySelectorType,
|
|
ToolProfileStatus,
|
|
ToolRateLimitWindowKind,
|
|
ToolRedactedValueSummary,
|
|
ToolRiskLevel,
|
|
ToolRuntimeKind,
|
|
ToolRuntimeSlotStatus,
|
|
} from "@paperclipai/shared";
|
|
import { agents } from "./agents.js";
|
|
import { approvals } from "./approvals.js";
|
|
import { companies } from "./companies.js";
|
|
import { executionWorkspaces } from "./execution_workspaces.js";
|
|
import { heartbeatRuns } from "./heartbeat_runs.js";
|
|
import { issueThreadInteractions } from "./issue_thread_interactions.js";
|
|
import { issues } from "./issues.js";
|
|
import { plugins } from "./plugins.js";
|
|
import { projects } from "./projects.js";
|
|
import { projectWorkspaces } from "./project_workspaces.js";
|
|
|
|
export const toolApplications = pgTable(
|
|
"tool_applications",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
applicationKey: text("application_key"),
|
|
name: text("name").notNull(),
|
|
description: text("description"),
|
|
type: text("type").$type<ToolApplicationType>().notNull(),
|
|
status: text("status").$type<ToolApplicationStatus>().notNull().default("active"),
|
|
pluginId: uuid("plugin_id").references(() => plugins.id, { onDelete: "set null" }),
|
|
ownerAgentId: uuid("owner_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
ownerUserId: text("owner_user_id"),
|
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
|
|
archivedAt: timestamp("archived_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_applications_company_idx").on(table.companyId),
|
|
index("tool_applications_company_status_idx").on(table.companyId, table.status),
|
|
uniqueIndex("tool_applications_company_name_uq").on(table.companyId, table.name),
|
|
uniqueIndex("tool_applications_company_key_uq").on(table.companyId, table.applicationKey),
|
|
],
|
|
);
|
|
|
|
export const toolConnections = pgTable(
|
|
"tool_connections",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
// NO ACTION (not CASCADE) so the database itself refuses to delete an application that still
|
|
// has connections. This closes the delete-vs-create race in DELETE
|
|
// /tool-applications/:applicationId: a connection inserted concurrently takes a FOR KEY SHARE
|
|
// lock on the parent row, so the delete fails closed with a foreign-key violation instead of
|
|
// silently cascading the new connection away. NO ACTION (checked at end-of-statement) rather
|
|
// than RESTRICT (checked immediately) is deliberate: it lets a company delete still cascade —
|
|
// companies → tool_applications and companies → tool_connections both fire in one statement,
|
|
// and the connections are gone by the time this constraint is checked.
|
|
applicationId: uuid("application_id").notNull().references(() => toolApplications.id, { onDelete: "no action" }),
|
|
name: text("name").notNull(),
|
|
uid: text("uid").notNull(),
|
|
connectionKind: text("connection_kind").$type<ToolConnectionKind>().notNull().default("managed"),
|
|
ownership: text("ownership").$type<ToolConnectionOwnership>().notNull().default("customer"),
|
|
transport: text("transport").$type<ToolConnectionTransport>().notNull(),
|
|
authKind: text("auth_kind").$type<ToolConnectionAuthKind>().notNull().default("none"),
|
|
status: text("status").$type<ToolConnectionStatus>().notNull().default("draft"),
|
|
enabled: boolean("enabled").notNull().default(false),
|
|
config: jsonb("config").$type<Record<string, unknown>>().notNull().default({}),
|
|
transportConfig: jsonb("transport_config").$type<Record<string, unknown>>().notNull().default({}),
|
|
credentialRefs: jsonb("credential_refs").$type<McpConnectionCredentialRef[]>().notNull().default([]),
|
|
credentialSecretRefs: jsonb("credential_secret_refs").$type<ToolCredentialSecretRef[]>().notNull().default([]),
|
|
healthStatus: text("health_status").$type<ToolConnectionHealthStatus>().notNull().default("unchecked"),
|
|
healthMessage: text("health_message"),
|
|
healthCheckedAt: timestamp("health_checked_at", { withTimezone: true }),
|
|
lastHealthAt: timestamp("last_health_at", { withTimezone: true }),
|
|
lastCatalogRefreshAt: timestamp("last_catalog_refresh_at", { withTimezone: true }),
|
|
lastError: text("last_error"),
|
|
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
createdByUserId: text("created_by_user_id"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
check("tool_connections_ownership_check", sql`${table.ownership} in ('platform_shared', 'platform_provisioned', 'customer', 'dcr')`),
|
|
check("tool_connections_transport_check", sql`${table.transport} in ('mcp_remote', 'rest_api', 'local_stdio')`),
|
|
check("tool_connections_auth_kind_check", sql`${table.authKind} in ('oauth', 'api_key', 'none')`),
|
|
index("tool_connections_company_idx").on(table.companyId),
|
|
index("tool_connections_application_idx").on(table.applicationId),
|
|
index("tool_connections_company_enabled_idx").on(table.companyId, table.enabled),
|
|
uniqueIndex("tool_connections_company_name_uq").on(table.companyId, table.name),
|
|
uniqueIndex("tool_connections_company_uid_uq").on(table.companyId, table.uid),
|
|
unique("tool_connections_company_id_uq").on(table.companyId, table.id),
|
|
],
|
|
);
|
|
|
|
export const connectionGrants = pgTable(
|
|
"connection_grants",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
connectionId: uuid("connection_id").notNull(),
|
|
kind: text("kind").$type<ConnectionGrantKind>().notNull(),
|
|
subjectUserId: text("subject_user_id"),
|
|
providerTenant: jsonb("provider_tenant").$type<{ name?: string; externalId?: string }>(),
|
|
credentialSecretRefs: jsonb("credential_secret_refs").$type<ToolCredentialSecretRef[]>().notNull().default([]),
|
|
status: text("status").$type<ConnectionGrantStatus>().notNull().default("active"),
|
|
isDefault: boolean("is_default").notNull().default(false),
|
|
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
createdByUserId: text("created_by_user_id"),
|
|
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
|
revokedByAgentId: uuid("revoked_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
revokedByUserId: text("revoked_by_user_id"),
|
|
lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
check("connection_grants_kind_check", sql`${table.kind} in ('workspace', 'user')`),
|
|
check("connection_grants_status_check", sql`${table.status} in ('active', 'revoked', 'expired', 'needs_reauthorization')`),
|
|
check("connection_grants_subject_check", sql`(${table.kind} = 'user' and ${table.subjectUserId} is not null) or (${table.kind} = 'workspace' and ${table.subjectUserId} is null)`),
|
|
check("connection_grants_default_check", sql`${table.isDefault} = false or ${table.kind} = 'workspace'`),
|
|
foreignKey({
|
|
columns: [table.companyId, table.connectionId],
|
|
foreignColumns: [toolConnections.companyId, toolConnections.id],
|
|
name: "connection_grants_company_connection_fk",
|
|
}).onDelete("cascade"),
|
|
index("connection_grants_company_connection_idx").on(table.companyId, table.connectionId),
|
|
index("connection_grants_subject_user_idx").on(table.companyId, table.subjectUserId),
|
|
uniqueIndex("connection_grants_user_uq").on(table.connectionId, table.subjectUserId),
|
|
uniqueIndex("connection_grants_default_uq").on(table.connectionId).where(sql`${table.isDefault} = true and ${table.kind} = 'workspace'`),
|
|
],
|
|
);
|
|
|
|
export const toolConnectionInstalls = pgTable(
|
|
"tool_connection_installs",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
connectionId: uuid("connection_id").notNull().references(() => toolConnections.id, { onDelete: "cascade" }),
|
|
targetType: text("target_type").$type<ToolConnectionInstallTargetType>().notNull(),
|
|
targetId: text("target_id").notNull(),
|
|
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
createdByUserId: text("created_by_user_id"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
check("tool_connection_installs_target_type_check", sql`${table.targetType} in ('company', 'agent')`),
|
|
index("tool_connection_installs_company_target_idx").on(table.companyId, table.targetType, table.targetId),
|
|
index("tool_connection_installs_connection_idx").on(table.companyId, table.connectionId),
|
|
uniqueIndex("tool_connection_installs_target_uq").on(
|
|
table.companyId,
|
|
table.connectionId,
|
|
table.targetType,
|
|
table.targetId,
|
|
),
|
|
],
|
|
);
|
|
|
|
export const toolOauthStates = pgTable(
|
|
"tool_oauth_states",
|
|
{
|
|
state: text("state").primaryKey(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
connectionId: uuid("connection_id").notNull().references(() => toolConnections.id, { onDelete: "cascade" }),
|
|
codeVerifier: text("code_verifier").notNull(),
|
|
createdByActorType: text("created_by_actor_type"),
|
|
createdByActorId: text("created_by_actor_id"),
|
|
createdBySessionId: text("created_by_session_id"),
|
|
subjectUserId: text("subject_user_id"),
|
|
requestedScopes: jsonb("requested_scopes").$type<string[]>(),
|
|
returnTo: text("return_to"),
|
|
issueId: uuid("issue_id"),
|
|
interactionId: uuid("interaction_id"),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_oauth_states_company_idx").on(table.companyId),
|
|
index("tool_oauth_states_connection_idx").on(table.connectionId),
|
|
index("tool_oauth_states_actor_idx").on(table.createdByActorType, table.createdByActorId),
|
|
index("tool_oauth_states_expires_at_idx").on(table.expiresAt),
|
|
],
|
|
);
|
|
|
|
export const toolCatalogEntries = pgTable(
|
|
"tool_catalog_entries",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
applicationId: uuid("application_id").references(() => toolApplications.id, { onDelete: "cascade" }),
|
|
connectionId: uuid("connection_id").notNull().references(() => toolConnections.id, { onDelete: "cascade" }),
|
|
entryKind: text("entry_kind").$type<ToolCatalogEntryKind>().notNull().default("tool"),
|
|
name: text("name").notNull(),
|
|
toolName: text("tool_name").notNull(),
|
|
title: text("title"),
|
|
description: text("description"),
|
|
inputSchema: jsonb("input_schema").$type<Record<string, unknown>>().notNull().default({}),
|
|
outputSchema: jsonb("output_schema").$type<Record<string, unknown>>(),
|
|
annotations: jsonb("annotations").$type<Record<string, unknown>>().notNull().default({}),
|
|
riskLevel: text("risk_level").$type<ToolRiskLevel>().notNull().default("read"),
|
|
isReadOnly: boolean("is_read_only").notNull().default(true),
|
|
isWrite: boolean("is_write").notNull().default(false),
|
|
isDestructive: boolean("is_destructive").notNull().default(false),
|
|
status: text("status").$type<ToolCatalogEntryStatus>().notNull().default("active"),
|
|
version: text("version"),
|
|
versionHash: text("version_hash").notNull(),
|
|
schemaHash: text("schema_hash"),
|
|
firstSeenAt: timestamp("first_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }).notNull().defaultNow(),
|
|
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
|
|
reviewedByAgentId: uuid("reviewed_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
reviewedByUserId: text("reviewed_by_user_id"),
|
|
quarantinedAt: timestamp("quarantined_at", { withTimezone: true }),
|
|
quarantineReason: text("quarantine_reason"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_catalog_entries_company_idx").on(table.companyId),
|
|
index("tool_catalog_entries_application_idx").on(table.applicationId),
|
|
index("tool_catalog_entries_connection_idx").on(table.connectionId),
|
|
index("tool_catalog_entries_company_status_idx").on(table.companyId, table.status),
|
|
uniqueIndex("tool_catalog_entries_connection_name_uq").on(table.connectionId, table.name),
|
|
],
|
|
);
|
|
|
|
export const toolProfiles = pgTable(
|
|
"tool_profiles",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
profileKey: text("profile_key").notNull(),
|
|
name: text("name").notNull(),
|
|
description: text("description"),
|
|
status: text("status").$type<ToolProfileStatus>().notNull().default("active"),
|
|
defaultAction: text("default_action").$type<ToolProfileDefaultAction>().notNull().default("deny"),
|
|
newToolsReviewedAt: timestamp("new_tools_reviewed_at", { withTimezone: true }),
|
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_profiles_company_status_idx").on(table.companyId, table.status),
|
|
uniqueIndex("tool_profiles_company_key_uq").on(table.companyId, table.profileKey),
|
|
uniqueIndex("tool_profiles_company_name_uq").on(table.companyId, table.name),
|
|
],
|
|
);
|
|
|
|
export const toolProfileEntries = pgTable(
|
|
"tool_profile_entries",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
profileId: uuid("profile_id").notNull().references(() => toolProfiles.id, { onDelete: "cascade" }),
|
|
selectorType: text("selector_type").$type<ToolProfileEntrySelectorType>().notNull(),
|
|
effect: text("effect").$type<ToolProfileEntryEffect>().notNull().default("include"),
|
|
applicationId: uuid("application_id").references(() => toolApplications.id, { onDelete: "cascade" }),
|
|
connectionId: uuid("connection_id").references(() => toolConnections.id, { onDelete: "cascade" }),
|
|
catalogEntryId: uuid("catalog_entry_id").references(() => toolCatalogEntries.id, { onDelete: "cascade" }),
|
|
toolName: text("tool_name"),
|
|
riskLevel: text("risk_level").$type<ToolRiskLevel>(),
|
|
conditions: jsonb("conditions").$type<Record<string, unknown>>(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_profile_entries_company_profile_idx").on(table.companyId, table.profileId),
|
|
index("tool_profile_entries_application_idx").on(table.companyId, table.applicationId),
|
|
index("tool_profile_entries_connection_idx").on(table.companyId, table.connectionId),
|
|
index("tool_profile_entries_catalog_entry_idx").on(table.companyId, table.catalogEntryId),
|
|
],
|
|
);
|
|
|
|
export const toolProfileBindings = pgTable(
|
|
"tool_profile_bindings",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
profileId: uuid("profile_id").notNull().references(() => toolProfiles.id, { onDelete: "cascade" }),
|
|
targetType: text("target_type").$type<ToolProfileBindingTargetType>().notNull(),
|
|
targetId: text("target_id").notNull(),
|
|
priority: integer("priority").notNull().default(100),
|
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
|
|
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
createdByUserId: text("created_by_user_id"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_profile_bindings_company_target_idx").on(table.companyId, table.targetType, table.targetId),
|
|
uniqueIndex("tool_profile_bindings_target_profile_uq").on(
|
|
table.companyId,
|
|
table.targetType,
|
|
table.targetId,
|
|
table.profileId,
|
|
),
|
|
],
|
|
);
|
|
|
|
export const toolMcpGateways = pgTable(
|
|
"tool_mcp_gateways",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
gatewayPublicId: text("gateway_public_id").notNull().default(sql`'gw_' || replace(gen_random_uuid()::text, '-', '')`),
|
|
name: text("name").notNull(),
|
|
slug: text("slug").notNull(),
|
|
displaySlug: text("display_slug").notNull().default(""),
|
|
description: text("description"),
|
|
status: text("status").$type<ToolMcpGatewayStatus>().notNull().default("active"),
|
|
profileId: uuid("profile_id").notNull().references(() => toolProfiles.id, { onDelete: "restrict" }),
|
|
defaultProfileMode: text("default_profile_mode").$type<ToolMcpGatewayDefaultProfileMode>().notNull().default("gateway_only"),
|
|
contextScopeType: text("context_scope_type").$type<ToolMcpGatewayContextScopeType>().notNull().default("none"),
|
|
contextScopeId: text("context_scope_id"),
|
|
agentId: uuid("agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
projectId: uuid("project_id").references(() => projects.id, { onDelete: "set null" }),
|
|
issueId: uuid("issue_id").references(() => issues.id, { onDelete: "set null" }),
|
|
approvalIssueId: uuid("approval_issue_id").references(() => issues.id, { onDelete: "set null" }),
|
|
authConfig: jsonb("auth_config").$type<ToolMcpGatewayAuthConfig>().notNull().default({
|
|
version: 1,
|
|
bearer: {
|
|
enabled: true,
|
|
tokenPrefix: "pcgw",
|
|
defaultTtlSeconds: 7_776_000,
|
|
requireFiniteExpiry: true,
|
|
longLivedTokenRequiresOverride: true,
|
|
},
|
|
oauth: {
|
|
enabled: false,
|
|
reservedFor: "v1_5",
|
|
dynamicClientRegistration: false,
|
|
authorizationCodePkce: false,
|
|
},
|
|
}),
|
|
headerPolicy: jsonb("header_policy").$type<ToolMcpGatewayHeaderPolicy>().notNull().default({
|
|
version: 1,
|
|
callerPassthrough: { enabled: false, allowedHeaders: [] },
|
|
staticHeaders: [],
|
|
generatedMetadata: { enabled: false, allowedHeaders: [] },
|
|
responseHeaders: { forwardMcpRequiredHeaders: true, forwardSafeCacheHeaders: true },
|
|
}),
|
|
metadataPolicy: jsonb("metadata_policy").$type<ToolMcpGatewayMetadataPolicy>().notNull().default({
|
|
version: 1,
|
|
forwardCompanyId: false,
|
|
forwardGatewayId: false,
|
|
forwardProjectId: false,
|
|
forwardIssueId: false,
|
|
forwardAgentId: false,
|
|
forwardRunId: false,
|
|
forwardCorrelationId: true,
|
|
}),
|
|
onDemandToolsConfig: jsonb("on_demand_tools_config").$type<ToolMcpGatewayOnDemandToolsConfig>().notNull().default({
|
|
enabled: false,
|
|
searchToolName: "search_tools",
|
|
runToolName: "run_tool",
|
|
}),
|
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
|
|
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
createdByUserId: text("created_by_user_id"),
|
|
archivedAt: timestamp("archived_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_mcp_gateways_company_idx").on(table.companyId),
|
|
index("tool_mcp_gateways_company_status_idx").on(table.companyId, table.status),
|
|
index("tool_mcp_gateways_profile_idx").on(table.companyId, table.profileId),
|
|
uniqueIndex("tool_mcp_gateways_public_id_uq").on(table.gatewayPublicId),
|
|
uniqueIndex("tool_mcp_gateways_company_slug_uq").on(table.companyId, table.slug),
|
|
uniqueIndex("tool_mcp_gateways_company_name_uq").on(table.companyId, table.name),
|
|
],
|
|
);
|
|
|
|
export const toolMcpGatewayTokens = pgTable(
|
|
"tool_mcp_gateway_tokens",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
gatewayId: uuid("gateway_id").notNull().references(() => toolMcpGateways.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
tokenHash: text("token_hash").notNull(),
|
|
tokenPrefix: text("token_prefix").notNull().default(""),
|
|
subjectType: text("subject_type").$type<ToolMcpGatewayTokenSubjectType>().notNull().default("gateway_client"),
|
|
subjectId: text("subject_id"),
|
|
clientLabel: text("client_label").notNull().default(""),
|
|
ownerNote: text("owner_note").notNull().default(""),
|
|
allowedActions: jsonb("allowed_actions").$type<ToolMcpGatewayTokenAction[]>().notNull().default(["tools/list", "tools/call"]),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
expiryOverrideReason: text("expiry_override_reason"),
|
|
expiryOverrideByUserId: text("expiry_override_by_user_id"),
|
|
expiryOverrideByAgentId: uuid("expiry_override_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
expiryOverrideAt: timestamp("expiry_override_at", { withTimezone: true }),
|
|
lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
|
|
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
|
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
createdByUserId: text("created_by_user_id"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("tool_mcp_gateway_tokens_token_hash_uq").on(table.tokenHash),
|
|
index("tool_mcp_gateway_tokens_gateway_idx").on(table.companyId, table.gatewayId),
|
|
index("tool_mcp_gateway_tokens_subject_idx").on(table.companyId, table.subjectType, table.subjectId),
|
|
index("tool_mcp_gateway_tokens_company_expires_idx").on(table.companyId, table.expiresAt),
|
|
],
|
|
);
|
|
|
|
export const toolPolicies = pgTable(
|
|
"tool_policies",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
description: text("description"),
|
|
policyType: text("policy_type").$type<ToolPolicyType>().notNull(),
|
|
priority: integer("priority").notNull().default(100),
|
|
enabled: boolean("enabled").notNull().default(true),
|
|
selectors: jsonb("selectors").$type<Record<string, unknown>>().notNull().default({}),
|
|
conditions: jsonb("conditions").$type<Record<string, unknown>>(),
|
|
config: jsonb("config").$type<Record<string, unknown>>(),
|
|
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
createdByUserId: text("created_by_user_id"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_policies_company_enabled_idx").on(table.companyId, table.enabled),
|
|
index("tool_policies_company_type_idx").on(table.companyId, table.policyType),
|
|
uniqueIndex("tool_policies_company_name_uq").on(table.companyId, table.name),
|
|
],
|
|
);
|
|
|
|
export const toolRuntimeSlots = pgTable(
|
|
"tool_runtime_slots",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
applicationId: uuid("application_id").references(() => toolApplications.id, { onDelete: "set null" }),
|
|
connectionId: uuid("connection_id").references(() => toolConnections.id, { onDelete: "cascade" }),
|
|
projectWorkspaceId: uuid("project_workspace_id").references(() => projectWorkspaces.id, { onDelete: "set null" }),
|
|
executionWorkspaceId: uuid("execution_workspace_id").references(() => executionWorkspaces.id, { onDelete: "set null" }),
|
|
issueId: uuid("issue_id").references(() => issues.id, { onDelete: "set null" }),
|
|
ownerScopeType: text("owner_scope_type").notNull().default("connection"),
|
|
ownerScopeId: text("owner_scope_id"),
|
|
runtimeKind: text("runtime_kind").$type<ToolRuntimeKind>().notNull().default("local_stdio"),
|
|
slotKey: text("slot_key").notNull(),
|
|
status: text("status").$type<ToolRuntimeSlotStatus>().notNull().default("stopped"),
|
|
reuseKey: text("reuse_key"),
|
|
workspaceScope: text("workspace_scope"),
|
|
credentialScopeHash: text("credential_scope_hash"),
|
|
provider: text("provider"),
|
|
providerRef: text("provider_ref"),
|
|
processId: integer("process_id"),
|
|
commandTemplateKey: text("command_template_key"),
|
|
healthStatus: text("health_status").$type<ToolConnectionHealthStatus>().notNull().default("unchecked"),
|
|
healthMessage: text("health_message"),
|
|
lastHealthCheckAt: timestamp("last_health_check_at", { withTimezone: true }),
|
|
lastStartedAt: timestamp("last_started_at", { withTimezone: true }),
|
|
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
stoppedAt: timestamp("stopped_at", { withTimezone: true }),
|
|
lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
|
|
idleExpiresAt: timestamp("idle_expires_at", { withTimezone: true }),
|
|
idleDeadlineAt: timestamp("idle_deadline_at", { withTimezone: true }),
|
|
lastError: text("last_error"),
|
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_runtime_slots_company_idx").on(table.companyId),
|
|
index("tool_runtime_slots_connection_idx").on(table.connectionId),
|
|
index("tool_runtime_slots_execution_workspace_idx").on(table.companyId, table.executionWorkspaceId),
|
|
uniqueIndex("tool_runtime_slots_slot_key_uq").on(table.companyId, table.slotKey),
|
|
],
|
|
);
|
|
|
|
export const toolStdioCommandTemplates = pgTable(
|
|
"tool_stdio_command_templates",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
templateKey: text("template_key").notNull(),
|
|
name: text("name").notNull(),
|
|
description: text("description"),
|
|
status: text("status").$type<"active" | "disabled">().notNull().default("active"),
|
|
command: text("command").notNull(),
|
|
args: jsonb("args").$type<string[]>().notNull().default([]),
|
|
envKeys: jsonb("env_keys").$type<string[]>().notNull().default([]),
|
|
tools: jsonb("tools").$type<Array<Record<string, unknown>>>().notNull().default([]),
|
|
createdByAgentId: uuid("created_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
createdByUserId: text("created_by_user_id"),
|
|
disabledAt: timestamp("disabled_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_stdio_command_templates_company_idx").on(table.companyId),
|
|
index("tool_stdio_command_templates_company_status_idx").on(table.companyId, table.status),
|
|
uniqueIndex("tool_stdio_command_templates_company_key_uq").on(table.companyId, table.templateKey),
|
|
],
|
|
);
|
|
|
|
export const toolGatewaySessions = pgTable(
|
|
"tool_gateway_sessions",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
agentId: uuid("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
runId: uuid("run_id").notNull().references(() => heartbeatRuns.id, { onDelete: "cascade" }),
|
|
issueId: uuid("issue_id").references(() => issues.id, { onDelete: "set null" }),
|
|
projectId: uuid("project_id").references(() => projects.id, { onDelete: "set null" }),
|
|
gatewayId: uuid("gateway_id").references(() => toolMcpGateways.id, { onDelete: "set null" }),
|
|
gatewayTokenId: uuid("gateway_token_id").references(() => toolMcpGatewayTokens.id, { onDelete: "set null" }),
|
|
gatewayPublicId: text("gateway_public_id"),
|
|
clientSubjectType: text("client_subject_type").$type<ToolMcpGatewayTokenSubjectType>(),
|
|
clientSubjectId: text("client_subject_id"),
|
|
clientName: text("client_name"),
|
|
mcpSessionId: text("mcp_session_id"),
|
|
correlationId: text("correlation_id"),
|
|
tokenHash: text("token_hash").notNull(),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
|
|
revokedAt: timestamp("revoked_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
uniqueIndex("tool_gateway_sessions_token_hash_uq").on(table.tokenHash),
|
|
index("tool_gateway_sessions_company_agent_idx").on(table.companyId, table.agentId),
|
|
index("tool_gateway_sessions_company_expires_idx").on(table.companyId, table.expiresAt),
|
|
index("tool_gateway_sessions_run_idx").on(table.companyId, table.runId),
|
|
index("tool_gateway_sessions_issue_idx").on(table.companyId, table.issueId),
|
|
index("tool_gateway_sessions_gateway_idx").on(table.companyId, table.gatewayId),
|
|
],
|
|
);
|
|
|
|
export const toolGatewayRateLimitCounters = pgTable(
|
|
"tool_gateway_rate_limit_counters",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
counterKey: text("counter_key").notNull(),
|
|
windowStartAt: timestamp("window_start_at", { withTimezone: true }).notNull(),
|
|
windowMs: integer("window_ms").notNull(),
|
|
limit: integer("limit").notNull(),
|
|
count: integer("count").notNull().default(0),
|
|
resetAt: timestamp("reset_at", { withTimezone: true }).notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_gateway_rate_limit_counters_company_idx").on(table.companyId),
|
|
uniqueIndex("tool_gateway_rate_limit_counters_window_uq").on(
|
|
table.companyId,
|
|
table.counterKey,
|
|
table.windowStartAt,
|
|
),
|
|
],
|
|
);
|
|
|
|
export const toolInvocations = pgTable(
|
|
"tool_invocations",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
idempotencyKey: text("idempotency_key"),
|
|
actorType: text("actor_type").notNull().default("system"),
|
|
actorId: text("actor_id"),
|
|
agentId: uuid("agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
issueId: uuid("issue_id").references(() => issues.id, { onDelete: "set null" }),
|
|
runId: uuid("run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }),
|
|
gatewayId: uuid("gateway_id").references(() => toolMcpGateways.id, { onDelete: "set null" }),
|
|
gatewayTokenId: uuid("gateway_token_id").references(() => toolMcpGatewayTokens.id, { onDelete: "set null" }),
|
|
gatewayPublicId: text("gateway_public_id"),
|
|
clientSubjectType: text("client_subject_type").$type<ToolMcpGatewayTokenSubjectType>(),
|
|
clientSubjectId: text("client_subject_id"),
|
|
clientName: text("client_name"),
|
|
mcpSessionId: text("mcp_session_id"),
|
|
correlationId: text("correlation_id"),
|
|
applicationId: uuid("application_id").references(() => toolApplications.id, { onDelete: "set null" }),
|
|
connectionId: uuid("connection_id").references(() => toolConnections.id, { onDelete: "set null" }),
|
|
catalogEntryId: uuid("catalog_entry_id").references(() => toolCatalogEntries.id, { onDelete: "set null" }),
|
|
catalogVersionHash: text("catalog_version_hash"),
|
|
catalogSchemaHash: text("catalog_schema_hash"),
|
|
providerType: text("provider_type"),
|
|
applicationKey: text("application_key"),
|
|
upstreamToolName: text("upstream_tool_name"),
|
|
riskLevel: text("risk_level").$type<ToolRiskLevel>(),
|
|
toolName: text("tool_name").notNull(),
|
|
argumentsHash: text("arguments_hash"),
|
|
argumentsSummary: jsonb("arguments_summary").$type<ToolRedactedValueSummary>(),
|
|
policyDecision: text("policy_decision").$type<ToolPolicyDecision>(),
|
|
matchedPolicyIds: jsonb("matched_policy_ids").$type<string[]>().notNull().default([]),
|
|
policyExplanation: jsonb("policy_explanation").$type<Record<string, unknown>>(),
|
|
credentialScopeSummary: jsonb("credential_scope_summary").$type<Record<string, unknown>>(),
|
|
headerPolicySummary: jsonb("header_policy_summary").$type<Record<string, unknown>>(),
|
|
approvalState: text("approval_state").$type<ToolInvocationApprovalState>().notNull().default("not_required"),
|
|
status: text("status").$type<ToolInvocationStatus>().notNull().default("pending"),
|
|
upstreamRequestId: text("upstream_request_id"),
|
|
resultHash: text("result_hash"),
|
|
resultSummary: jsonb("result_summary").$type<ToolRedactedValueSummary>(),
|
|
resultSizeBytes: integer("result_size_bytes"),
|
|
resultArtifactId: uuid("result_artifact_id"),
|
|
errorCode: text("error_code"),
|
|
errorMessage: text("error_message"),
|
|
startedAt: timestamp("started_at", { withTimezone: true }),
|
|
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_invocations_company_created_idx").on(table.companyId, table.createdAt),
|
|
index("tool_invocations_run_idx").on(table.companyId, table.runId),
|
|
index("tool_invocations_issue_idx").on(table.companyId, table.issueId),
|
|
index("tool_invocations_gateway_idx").on(table.companyId, table.gatewayId),
|
|
uniqueIndex("tool_invocations_company_idempotency_uq").on(table.companyId, table.idempotencyKey),
|
|
],
|
|
);
|
|
|
|
export const toolActionRequests = pgTable(
|
|
"tool_action_requests",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
invocationId: uuid("invocation_id").notNull().references(() => toolInvocations.id, { onDelete: "cascade" }),
|
|
issueId: uuid("issue_id").references(() => issues.id, { onDelete: "set null" }),
|
|
interactionId: uuid("interaction_id").references(() => issueThreadInteractions.id, { onDelete: "set null" }),
|
|
approvalId: uuid("approval_id").references(() => approvals.id, { onDelete: "set null" }),
|
|
status: text("status").$type<ToolActionRequestStatus>().notNull().default("pending"),
|
|
canonicalArgumentsHash: text("canonical_arguments_hash").notNull(),
|
|
canonicalArgumentsSummary: jsonb("canonical_arguments_summary").$type<ToolRedactedValueSummary>().notNull(),
|
|
signedArguments: text("signed_arguments"),
|
|
previewMarkdown: text("preview_markdown"),
|
|
requestedByAgentId: uuid("requested_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
requestedByUserId: text("requested_by_user_id"),
|
|
resolvedByAgentId: uuid("resolved_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
resolvedByUserId: text("resolved_by_user_id"),
|
|
decidedByAgentId: uuid("decided_by_agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
decidedByUserId: text("decided_by_user_id"),
|
|
decidedAt: timestamp("decided_at", { withTimezone: true }),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
resolvedAt: timestamp("resolved_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_action_requests_company_status_idx").on(table.companyId, table.status),
|
|
index("tool_action_requests_invocation_idx").on(table.invocationId),
|
|
index("tool_action_requests_issue_idx").on(table.companyId, table.issueId),
|
|
],
|
|
);
|
|
|
|
export const toolCallEvents = pgTable(
|
|
"tool_call_events",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
eventType: text("event_type").$type<ToolAuditEventType>().notNull(),
|
|
actorType: text("actor_type").notNull().default("system"),
|
|
actorId: text("actor_id"),
|
|
agentId: uuid("agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
runId: uuid("run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }),
|
|
issueId: uuid("issue_id").references(() => issues.id, { onDelete: "set null" }),
|
|
gatewayId: uuid("gateway_id").references(() => toolMcpGateways.id, { onDelete: "set null" }),
|
|
gatewayTokenId: uuid("gateway_token_id").references(() => toolMcpGatewayTokens.id, { onDelete: "set null" }),
|
|
gatewayPublicId: text("gateway_public_id"),
|
|
clientSubjectType: text("client_subject_type").$type<ToolMcpGatewayTokenSubjectType>(),
|
|
clientSubjectId: text("client_subject_id"),
|
|
clientName: text("client_name"),
|
|
mcpSessionId: text("mcp_session_id"),
|
|
correlationId: text("correlation_id"),
|
|
applicationId: uuid("application_id").references(() => toolApplications.id, { onDelete: "set null" }),
|
|
connectionId: uuid("connection_id").references(() => toolConnections.id, { onDelete: "set null" }),
|
|
catalogEntryId: uuid("catalog_entry_id").references(() => toolCatalogEntries.id, { onDelete: "set null" }),
|
|
invocationId: uuid("invocation_id").references(() => toolInvocations.id, { onDelete: "set null" }),
|
|
actionRequestId: uuid("action_request_id").references(() => toolActionRequests.id, { onDelete: "set null" }),
|
|
runtimeSlotId: uuid("runtime_slot_id").references(() => toolRuntimeSlots.id, { onDelete: "set null" }),
|
|
toolName: text("tool_name"),
|
|
decision: text("decision").$type<ToolPolicyDecision>(),
|
|
matchedPolicyIds: jsonb("matched_policy_ids").$type<string[]>().notNull().default([]),
|
|
reasonCode: text("reason_code"),
|
|
policyExplanation: jsonb("policy_explanation").$type<Record<string, unknown>>(),
|
|
credentialScopeSummary: jsonb("credential_scope_summary").$type<Record<string, unknown>>(),
|
|
headerPolicySummary: jsonb("header_policy_summary").$type<Record<string, unknown>>(),
|
|
outcome: text("outcome").$type<ToolAuditOutcome>().notNull().default("pending"),
|
|
latencyMs: integer("latency_ms"),
|
|
argumentsSummary: jsonb("arguments_summary").$type<ToolRedactedValueSummary>(),
|
|
requestHash: text("request_hash"),
|
|
requestSummary: jsonb("request_summary").$type<ToolRedactedValueSummary>(),
|
|
resultHash: text("result_hash"),
|
|
resultSummary: jsonb("result_summary").$type<ToolRedactedValueSummary>(),
|
|
resultSizeBytes: integer("result_size_bytes"),
|
|
redactionPlan: jsonb("redaction_plan").$type<Record<string, unknown>>(),
|
|
rateLimitState: jsonb("rate_limit_state").$type<Record<string, unknown>>(),
|
|
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
|
|
errorCode: text("error_code"),
|
|
errorMessage: text("error_message"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_call_events_company_created_idx").on(table.companyId, table.createdAt),
|
|
index("tool_call_events_run_idx").on(table.companyId, table.runId),
|
|
index("tool_call_events_issue_idx").on(table.companyId, table.issueId),
|
|
index("tool_call_events_invocation_idx").on(table.invocationId),
|
|
index("tool_call_events_gateway_idx").on(table.companyId, table.gatewayId),
|
|
],
|
|
);
|
|
|
|
export const connectionTokenIssuances = pgTable(
|
|
"connection_token_issuances",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
applicationId: uuid("application_id").references(() => toolApplications.id, { onDelete: "set null" }),
|
|
connectionId: uuid("connection_id").notNull().references(() => toolConnections.id, { onDelete: "cascade" }),
|
|
agentId: uuid("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
runId: uuid("run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }),
|
|
issueId: uuid("issue_id").references(() => issues.id, { onDelete: "set null" }),
|
|
projectId: uuid("project_id").references(() => projects.id, { onDelete: "set null" }),
|
|
responsibleUserId: text("responsible_user_id"),
|
|
path: text("path").$type<ConnectionTokenIssuancePath>().notNull(),
|
|
requestedScope: jsonb("requested_scope").$type<string[]>().notNull().default([]),
|
|
issuedScope: jsonb("issued_scope").$type<string[]>().notNull().default([]),
|
|
ttlSeconds: integer("ttl_seconds"),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
tokenHash: text("token_hash"),
|
|
outcome: text("outcome").$type<ConnectionTokenIssuanceOutcome>().notNull(),
|
|
errorCode: text("error_code"),
|
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().notNull().default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("connection_token_issuances_company_created_idx").on(table.companyId, table.createdAt),
|
|
index("connection_token_issuances_connection_created_idx").on(table.companyId, table.connectionId, table.createdAt),
|
|
index("connection_token_issuances_agent_connection_idx").on(table.companyId, table.agentId, table.connectionId, table.createdAt),
|
|
index("connection_token_issuances_run_idx").on(table.companyId, table.runId),
|
|
sql`CONSTRAINT connection_token_issuances_path_check CHECK (${table.path} IN ('exchange', 'oauth_access', 'static'))`,
|
|
sql`CONSTRAINT connection_token_issuances_outcome_check CHECK (${table.outcome} IN ('success', 'denied', 'rate_limited', 'use_env_lease', 'upstream_error', 'failure'))`,
|
|
sql`CONSTRAINT connection_token_issuances_ttl_bounds CHECK (${table.ttlSeconds} IS NULL OR (${table.ttlSeconds} >= 1 AND ${table.ttlSeconds} <= 900))`,
|
|
sql`CONSTRAINT connection_token_issuances_token_hash_format CHECK (${table.tokenHash} IS NULL OR ${table.tokenHash} ~ '^[a-f0-9]{64}$')`,
|
|
],
|
|
);
|
|
|
|
export const toolRateLimitCounters = pgTable(
|
|
"tool_rate_limit_counters",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
policyId: uuid("policy_id").notNull().references(() => toolPolicies.id, { onDelete: "cascade" }),
|
|
counterKey: text("counter_key").notNull(),
|
|
scopeType: text("scope_type").notNull(),
|
|
scopeId: text("scope_id").notNull(),
|
|
windowKind: text("window_kind").$type<ToolRateLimitWindowKind>().notNull(),
|
|
windowStartAt: timestamp("window_start_at", { withTimezone: true }).notNull(),
|
|
limit: integer("limit").notNull(),
|
|
remaining: integer("remaining").notNull(),
|
|
resetAt: timestamp("reset_at", { withTimezone: true }).notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_rate_limit_counters_company_idx").on(table.companyId),
|
|
uniqueIndex("tool_rate_limit_counters_window_uq").on(
|
|
table.companyId,
|
|
table.policyId,
|
|
table.counterKey,
|
|
table.windowKind,
|
|
table.windowStartAt,
|
|
),
|
|
],
|
|
);
|
|
|
|
export const toolRuntimeMetricCounters = pgTable(
|
|
"tool_runtime_metric_counters",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
metric: text("metric").notNull(),
|
|
bucketStartAt: timestamp("bucket_start_at", { withTimezone: true }).notNull(),
|
|
count: integer("count").notNull().default(0),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_runtime_metric_counters_company_metric_idx").on(table.companyId, table.metric, table.bucketStartAt),
|
|
uniqueIndex("tool_runtime_metric_counters_bucket_uq").on(table.companyId, table.metric, table.bucketStartAt),
|
|
sql`CONSTRAINT tool_runtime_metric_counters_count_nonnegative CHECK (${table.count} >= 0)`,
|
|
],
|
|
);
|
|
|
|
export const toolAccessAuditEvents = pgTable(
|
|
"tool_access_audit_events",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
|
gatewayId: uuid("gateway_id").references(() => toolMcpGateways.id, { onDelete: "set null" }),
|
|
gatewayTokenId: uuid("gateway_token_id").references(() => toolMcpGatewayTokens.id, { onDelete: "set null" }),
|
|
gatewayPublicId: text("gateway_public_id"),
|
|
clientName: text("client_name"),
|
|
correlationId: text("correlation_id"),
|
|
connectionId: uuid("connection_id").references(() => toolConnections.id, { onDelete: "set null" }),
|
|
catalogEntryId: uuid("catalog_entry_id").references(() => toolCatalogEntries.id, { onDelete: "set null" }),
|
|
actorType: text("actor_type").notNull().default("system"),
|
|
actorId: text("actor_id"),
|
|
action: text("action").notNull(),
|
|
outcome: text("outcome").notNull(),
|
|
reasonCode: text("reason_code"),
|
|
details: jsonb("details").$type<Record<string, unknown>>().notNull().default({}),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("tool_access_audit_company_created_idx").on(table.companyId, table.createdAt),
|
|
index("tool_access_audit_connection_idx").on(table.connectionId),
|
|
index("tool_access_audit_gateway_idx").on(table.companyId, table.gatewayId),
|
|
],
|
|
);
|