From 20ccf3f476d9ded61a16ea4bb5392411b349238a Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Sat, 29 Aug 2026 12:08:33 -0500 Subject: [PATCH] feat(apps): add connection grants and delegated identities (#12341) ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - External tools need explicit identity and access boundaries. > - Shared connection credentials cannot represent every user-scoped use case. > - Grants must stay company-scoped and support safe delegation. > - This pull request adds connection grants, identity rules, and their database contract. > - The benefit is durable control over which identity an agent may use. ## Linked Issues or Issue Description Refs #11965 This is stack 3 of 11. It depends on stack 2 and replaces another reviewable part of #11965. ## What Changed - Add company and user connection grants. - Add delegated identity and membership rules. - Synchronize database, shared, server, and UI contracts. - Register the grant-member replacement route in the OpenAPI surface in the same layer that mounts it. - Add migration 0231 with replay-safe guards and coverage. ## Verification - `pnpm -r typecheck` - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/tool-access-service.test.ts` - `pnpm --filter @paperclipai/server exec vitest run src/__tests__/openapi-routes.test.ts` (5 passed) - `pnpm --filter @paperclipai/db check:migrations` - `pnpm build` ## Risks - Incorrect grant selection could expose the wrong credential scope. - The service enforces company and subject boundaries before credential use. - Migration 0231 is generated, ordered after 0230, and safe to replay. > I checked `ROADMAP.md`. This stack continues the existing app connection work from #11965 and does not duplicate another planned item. ## Model Used OpenAI Codex, GPT-5. The runtime model ID and context window were not exposed. The model used reasoning, tool use, and code execution. ## 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 linked a public issue or pull request with `Refs #` - [x] I have not referenced internal or instance-local Paperclip issues or links - [x] My branch name describes the change 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 --- doc/DEVELOPING.md | 2 + doc/SPEC-implementation.md | 6 +- doc/connections/POSTHOG.md | 6 +- doc/connections/SECURITY-THREAT-MODEL.md | 8 +- doc/connections/SMOKE-LAB-TUTORIAL.md | 2 +- ...ecution-workspaces-and-runtime-services.md | 30 + ...connection-grants-phase2-migration.test.ts | 34 + ...connection-grants-phase4-migration.test.ts | 343 + ...nnections-v3-schema-core-migration.test.ts | 3 + .../migrations/0232_fixed_hannibal_king.sql | 202 + .../db/src/migrations/meta/0232_snapshot.json | 41904 ++++++++++++++++ packages/db/src/migrations/meta/_journal.json | 7 + packages/db/src/schema/index.ts | 2 + .../src/schema/issue_thread_interactions.ts | 2 + packages/db/src/schema/tool_access.ts | 56 +- packages/shared/src/index.ts | 14 + packages/shared/src/types/attention.ts | 4 +- packages/shared/src/types/index.ts | 10 + packages/shared/src/types/issue.ts | 19 + packages/shared/src/types/tool-access.ts | 80 +- packages/shared/src/validators/index.ts | 4 + packages/shared/src/validators/issue.ts | 1 + packages/shared/src/validators/tool-access.ts | 29 +- .../access-routes-permissions-upgrade.test.ts | 91 + server/src/__tests__/access-service.test.ts | 655 +- .../src/__tests__/attention-service.test.ts | 19 + .../connection-grant-resolution.test.ts | 26 + .../__tests__/generic-mcp-connection.test.ts | 5 + .../__tests__/plugin-worker-manager.test.ts | 2 +- server/src/__tests__/smoke-lab.test.ts | 4 + .../tool-access-policy-service.test.ts | 2 +- .../src/__tests__/tool-access-service.test.ts | 858 +- .../__tests__/tool-connection-removal.test.ts | 8 +- .../tool-connection-visibility.test.ts | 27 + .../__tests__/tool-gateway-service.test.ts | 11 + server/src/__tests__/tool-gateway.test.ts | 352 +- .../src/__tests__/workspace-runtime.test.ts | 2 + server/src/middleware/error-handler.ts | 4 + server/src/routes/access.ts | 175 +- server/src/routes/issues.ts | 2 + server/src/routes/openapi.ts | 26 + server/src/routes/tool-access.ts | 269 +- server/src/services/access.ts | 356 +- .../attention-resolver-audience.test.ts | 1 + server/src/services/attention.ts | 8 +- ...ssue-thread-interaction-resolution.test.ts | 13 + .../issue-thread-interaction-resolution.ts | 19 +- .../src/services/issue-thread-interactions.ts | 7 + server/src/services/smoke-lab.ts | 22 + server/src/services/tool-access.ts | 934 +- server/src/services/tool-gateway.ts | 401 +- tests/e2e/app-not-connected.spec.ts | 50 +- tests/e2e/applications-crud.spec.ts | 2 +- tests/e2e/apps-dark-mode-shots.spec.ts | 2 + tests/e2e/apps-prosumer-mcp-flow.spec.ts | 26 +- tests/e2e/mcp-user-stories.spec.ts | 16 +- tests/e2e/playwright.config.ts | 1 + ui/src/api/tools.ts | 49 +- .../IssueThreadInteractionCard.test.tsx | 130 +- .../components/IssueThreadInteractionCard.tsx | 415 +- ui/src/components/MemberMultiSelect.tsx | 226 + ui/src/components/ui/radio-card.tsx | 28 +- .../issueThreadInteractionFixtures.ts | 56 + ui/src/lib/interaction-audience.test.ts | 38 + ui/src/lib/interaction-audience.ts | 34 +- ui/src/lib/queryKeys.ts | 2 + ui/src/pages/apps/AppDetail.test.tsx | 471 +- ui/src/pages/apps/AppDetail.tsx | 222 +- ui/src/pages/apps/AppsConnect.test.tsx | 346 +- ui/src/pages/apps/AppsConnect.tsx | 603 +- ui/src/pages/apps/Browse.test.tsx | 2 +- .../apps/app-detail/IdentitiesSection.tsx | 571 + .../apps/app-detail/PermissionsPanel.tsx | 458 +- ui/src/pages/apps/app-detail/SetupPanel.tsx | 83 +- ui/src/pages/apps/connection-identity.ts | 130 + ui/src/pages/apps/store-cards.tsx | 2 +- .../pages/tools/McpConfigHelpDialog.test.tsx | 15 +- ui/src/pages/tools/PasteConfigTab.test.tsx | 1 + .../stories/access-profiles.stories.tsx | 2 + .../stories/notion-connect-flow.stories.tsx | 3 +- .../permitted-vs-installed.stories.tsx | 239 +- .../personal-connection-identity.stories.tsx | 319 + 82 files changed, 50276 insertions(+), 1333 deletions(-) create mode 100644 packages/db/src/connection-grants-phase2-migration.test.ts create mode 100644 packages/db/src/connection-grants-phase4-migration.test.ts create mode 100644 packages/db/src/migrations/0232_fixed_hannibal_king.sql create mode 100644 packages/db/src/migrations/meta/0232_snapshot.json create mode 100644 server/src/__tests__/connection-grant-resolution.test.ts create mode 100644 server/src/__tests__/tool-connection-visibility.test.ts create mode 100644 ui/src/components/MemberMultiSelect.tsx create mode 100644 ui/src/pages/apps/app-detail/IdentitiesSection.tsx create mode 100644 ui/src/pages/apps/connection-identity.ts create mode 100644 ui/storybook/stories/personal-connection-identity.stories.tsx diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index cd5d36cbe9..a147b6a35b 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -735,6 +735,8 @@ For Tailscale HTTPS exposure, readiness includes stable listener-ownership check In Vite middleware mode, Paperclip gives HMR a dedicated HTTP server bound to the managed runtime's loopback host. The browser still derives the HMR hostname from the public HTTPS page, so listener containment does not break remote hot reload. +When a workspace service runs Paperclip for browser OAuth QA, configure its `expose.urlTemplate` with the canonical URL the browser can reach. Paperclip preserves explicit `PAPERCLIP_PUBLIC_URL` or `BETTER_AUTH_URL` settings; otherwise it uses a valid exposed HTTPS origin (or loopback HTTP) as the managed runtime fallback for Better Auth and `/api/tools/oauth/callback`. Internal service names such as `http://paperclip-dev:` are rejected unless that hostname is genuinely the browser route. Use a unique origin per isolated worktree. See [Execution Workspaces And Runtime Services](../docs/guides/board-operator/execution-workspaces-and-runtime-services.md#browser-reachable-origins-for-oauth-qa) for configuration and verification. + ## App-Shipped Skills Catalog The Paperclip app ships a curated catalog of company skills out of the box. The diff --git a/doc/SPEC-implementation.md b/doc/SPEC-implementation.md index 6254f5fedb..1d223016bc 100644 --- a/doc/SPEC-implementation.md +++ b/doc/SPEC-implementation.md @@ -703,8 +703,10 @@ old creator-excluding behavior as canonical `not_creator`, legacy `board_only` r become `human_only`, and both are marked `legacy_inherited_restriction`. Resolved outcomes and resolver attribution are immutable. -An explicit named addressee and a company-configured cap may narrow the effective -audience. A cap never widens the requested audience. Tool-action confirmations and +An explicit named agent or user addressee and a company-configured cap may narrow +the effective audience. Only the exact named addressee may resolve an addressed +interaction; a human does not override a user addressee. A cap never widens the +requested audience. Tool-action confirmations and other hard-governed action cards remain `human_only` (or move to the formal approval system) regardless of a requested open audience. diff --git a/doc/connections/POSTHOG.md b/doc/connections/POSTHOG.md index 0f59672b36..39a63035c8 100644 --- a/doc/connections/POSTHOG.md +++ b/doc/connections/POSTHOG.md @@ -79,9 +79,9 @@ values for either. 6. For OAuth, continue through browser consent. For API-key setup, create a personal API key using PostHog's **MCP Server** preset and paste it into Paperclip. Never put the key in connection configuration or a URL. -7. Review discovered actions. Known writes ask first, destructive or nested - execution tools remain quarantined, and unknown PostHog tools default to - write risk until reviewed. +7. Review discovered actions. Known writes and destructive actions default to + **Ask first**, and unknown PostHog tools default to write risk so they inherit + that approval gate unless the operator changes the selection. Paperclip sends the project scope as the `x-posthog-project-id` managed header. It sends configured `readonly`, `features`, `tools`, and `mode` values as query diff --git a/doc/connections/SECURITY-THREAT-MODEL.md b/doc/connections/SECURITY-THREAT-MODEL.md index 7fb880ccdc..3ec347ecf7 100644 --- a/doc/connections/SECURITY-THREAT-MODEL.md +++ b/doc/connections/SECURITY-THREAT-MODEL.md @@ -48,7 +48,13 @@ context and server-side ownership checks. 8. **External content is untrusted.** Provider responses, chat messages, documents, webhook payloads, and remote MCP outputs may contain prompt injection and must not widen grants or bypass approvals. - +9. **Link-local egress is always denied.** Operator-configured remote MCP and + OAuth URLs may reach intentional loopback, RFC 1918, or IPv6 ULA services in + local/private deployments, but never IPv4 `169.254.0.0/16` or IPv6 + `fe80::/10`. Every hostname is resolved once and pinned; DNS answers, the + connected socket peer, and every redirect are mediated before request bytes + are written. Public deployments continue to deny the broader private and + reserved address set. ## Protected Assets - OAuth tokens, refresh tokens, app-installation tokens, API keys, webhook diff --git a/doc/connections/SMOKE-LAB-TUTORIAL.md b/doc/connections/SMOKE-LAB-TUTORIAL.md index 754ed94055..c6093a9f43 100644 --- a/doc/connections/SMOKE-LAB-TUTORIAL.md +++ b/doc/connections/SMOKE-LAB-TUTORIAL.md @@ -57,7 +57,7 @@ the URL bar, e.g. `PAP`). Replace it in the example paths. ## 2. Open the Smoke Lab and start the services 1. In the left sidebar open **Apps**, then under the **Developer** section - ("Advanced setup for developers. Most teams never open this.") click + ("Advanced setup for developers.") click **Smoke Lab** (`/{PREFIX}/apps/advanced/smoke-lab`). The breadcrumb reads *Apps → Advanced setup → Smoke Lab*. 2. **You should see:** a *Developer tools* page header, then the **Smoke Lab** diff --git a/docs/guides/board-operator/execution-workspaces-and-runtime-services.md b/docs/guides/board-operator/execution-workspaces-and-runtime-services.md index 6d4e927400..e38b1b2692 100644 --- a/docs/guides/board-operator/execution-workspaces-and-runtime-services.md +++ b/docs/guides/board-operator/execution-workspaces-and-runtime-services.md @@ -65,6 +65,36 @@ Heartbeat resolves a workspace for the run (code location and session continuity 4. Heartbeat passes the resolved code workspace to the agent run. 5. Heartbeat calls `ensureRuntimeServicesForRun` to start the workspace's `running`-desired runtime services, running the lazy runtime provision command first if one is configured and has not yet run (see "Lazy runtime provisioning" below). +## Browser-reachable origins for OAuth QA + +A managed service that runs Paperclip itself needs one canonical origin for Better Auth and tool OAuth callbacks. Paperclip resolves that origin in this order: + +1. Explicit service/runtime configuration such as `PAPERCLIP_PUBLIC_URL` or `BETTER_AUTH_URL`. +2. An explicit instance auth public base URL. +3. The managed service's rendered `expose.urlTemplate`, injected as a low-priority runtime fallback. + +The exposed URL must describe the route the operator's browser actually uses. Non-loopback callbacks require HTTPS. Loopback HTTP such as `http://127.0.0.1:45439` is supported for local browser QA. A non-loopback hostname rendered from workspace data must remain inside the stable domain suffix configured by `expose.urlTemplate`; branch names cannot replace that domain. Bind addresses, internal-only single-label names such as `paperclip-dev`, reserved/non-resolving names, and non-loopback HTTP origins fail service startup with configuration guidance instead of silently producing an unusable redirect URI. + +Keep readiness and browser exposure separate when a proxy or tailnet route fronts the process: + +```json +{ + "name": "paperclip-dev", + "command": "pnpm dev --bind lan", + "port": { "type": "auto" }, + "readiness": { + "type": "http", + "urlTemplate": "http://127.0.0.1:{{port}}" + }, + "expose": { + "type": "url", + "urlTemplate": "https://{{workspace.branchName}}.dev.example.com" + } +} +``` + +Use a distinct reachable hostname (or other distinct origin) per isolated worktree. Do not point multiple worktree runtimes at the parent instance's origin. After startup, open the service URL in the same browser session used for QA and verify `GET /api/tools/oauth/client-metadata`; its `redirect_uris` entry should use that service origin and `/api/tools/oauth/callback`. + ## Lazy runtime provisioning Some workspaces need heavy one-time setup — seeding a database, warming caches — before their runtime services can start. That work can be deferred to the first runtime-service start instead of running eagerly during workspace preparation. diff --git a/packages/db/src/connection-grants-phase2-migration.test.ts b/packages/db/src/connection-grants-phase2-migration.test.ts new file mode 100644 index 0000000000..fb38043715 --- /dev/null +++ b/packages/db/src/connection-grants-phase2-migration.test.ts @@ -0,0 +1,34 @@ +import fs from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +const sql = fs.readFileSync(path.join(import.meta.dirname, "migrations/0232_fixed_hannibal_king.sql"), "utf8"); + +describe("connection grants phase 2 migration", () => { + it("renames workspace grants before restoring organization-only constraints", () => { + const rename = sql.indexOf(`UPDATE "connection_grants" SET "kind" = 'organization' WHERE "kind" = 'workspace'`); + const constraint = sql.indexOf(`"connection_grants"."kind" in ('organization', 'user')`); + expect(rename).toBeGreaterThan(-1); + expect(constraint).toBeGreaterThan(rename); + expect(sql).not.toContain(`CHECK ("connection_grants"."kind" in ('workspace', 'user'))`); + }); + + it("backfills a default organization grant only when a connection is missing one", () => { + expect(sql).toContain(`c."id", 'organization', c."credential_secret_refs", 'active', true`); + expect(sql).toContain(`WHERE NOT EXISTS`); + expect(sql).toContain(`g."connection_id" = c."id" AND g."is_default" = true`); + }); + + it("adds credential policy and the grant audience table", () => { + expect(sql).toContain(`CREATE TABLE IF NOT EXISTS "connection_grant_members"`); + expect(sql).toContain(`ADD COLUMN IF NOT EXISTS "credential_policy" text DEFAULT 'shared' NOT NULL`); + expect(sql).toContain(`'shared', 'per_user', 'per_user_with_fallback'`); + }); + + it("is safe to replay when schema state is ahead of the migration journal", () => { + expect(sql).toContain(`DROP CONSTRAINT IF EXISTS "connection_grant_members_company_grant_fk"`); + expect(sql).toContain(`DROP CONSTRAINT IF EXISTS "tool_connections_credential_policy_check"`); + expect(sql).toContain(`CREATE UNIQUE INDEX IF NOT EXISTS "connection_grants_default_uq"`); + expect(sql).toContain(`IF NOT EXISTS (\n\t\tSELECT 1\n\t\tFROM pg_constraint`); + }); +}); diff --git a/packages/db/src/connection-grants-phase4-migration.test.ts b/packages/db/src/connection-grants-phase4-migration.test.ts new file mode 100644 index 0000000000..1fb47371f1 --- /dev/null +++ b/packages/db/src/connection-grants-phase4-migration.test.ts @@ -0,0 +1,343 @@ +import { createHash, randomUUID } from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import postgres from "postgres"; +import { afterEach, describe, expect, it } from "vitest"; +import { applyPendingMigrations, inspectMigrations } from "./client.js"; +import { + getEmbeddedPostgresTestSupport, + startEmbeddedPostgresTestDatabase, +} from "./test-embedded-postgres.js"; + +const MIGRATION_FILE = "0232_fixed_hannibal_king.sql"; +const migrationSql = fs.readFileSync( + path.join(import.meta.dirname, "migrations", MIGRATION_FILE), + "utf8", +); +const cleanups: Array<() => Promise> = []; +const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); +const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; + +function migrationHash() { + return createHash("sha256").update(migrationSql).digest("hex"); +} + +afterEach(async () => { + while (cleanups.length > 0) await cleanups.pop()?.(); +}); + +describe("connection grants phase 4 migration", () => { + it("adds replay-safe named-agent standing delegations", () => { + expect(migrationSql).toContain(`CREATE TABLE IF NOT EXISTS "connection_grant_delegations"`); + expect(migrationSql).toContain(`"connection_grant_delegations_grant_agent_uq"`); + expect(migrationSql).toContain(`FOREIGN KEY ("company_id","grant_id")`); + expect(migrationSql).toContain(`DROP CONSTRAINT IF EXISTS "connection_grant_delegations_company_grant_fk"`); + expect(migrationSql).toContain(`CREATE INDEX IF NOT EXISTS "connection_grant_delegations_company_agent_idx"`); + expect(migrationSql).toContain(`ON CONFLICT DO NOTHING`); + }); + + it("remediates ambiguous personal-secret ownership without assigning an arbitrary owner", () => { + expect(migrationSql).toContain(`phase4_ambiguous_personal_secrets`); + expect(migrationSql).toContain(`count(DISTINCT g."subject_user_id") AS "owner_count"`); + expect(migrationSql).toContain(`organization_grant."kind" <> 'user'`); + expect(migrationSql).toContain(`FROM "company_secret_bindings" binding`); + expect(migrationSql).toContain(`FROM "routine_triggers" routine_trigger`); + expect(migrationSql).toContain(`grant_row."kind" = 'user'`); + expect(migrationSql).not.toContain(`UPDATE "tool_connections" connection_row`); + expect(migrationSql).toContain(`"status" = 'needs_reauthorization'`); + }); +}); + +describeEmbeddedPostgres("connection grants phase 4 executable migration", () => { + it("converts one owner and rejects two-owner or mixed legacy references", async () => { + const database = await startEmbeddedPostgresTestDatabase("paperclip-connection-phase4-migration-"); + cleanups.push(database.cleanup); + const sql = postgres(database.connectionString, { max: 1, onnotice: () => {} }); + + async function rewindMigration() { + await sql`DELETE FROM "drizzle"."__drizzle_migrations" WHERE "hash" = ${migrationHash()}`; + expect(await inspectMigrations(database.connectionString)).toMatchObject({ + status: "needsMigrations", + pendingMigrations: [MIGRATION_FILE], + }); + } + + async function seedLegacyCredential(input: { + ownerUserIds: string[]; + includeOrganizationGrant?: boolean; + includeConnectionReference?: boolean; + includeCompanyBinding?: boolean; + includeRoutineTrigger?: boolean; + credentialPolicy?: "shared" | "per_user" | "per_user_with_fallback"; + }) { + const companyId = randomUUID(); + const applicationId = randomUUID(); + const connectionId = randomUUID(); + const secretId = randomUUID(); + await sql` + INSERT INTO "companies" ("id", "name", "issue_prefix") + VALUES (${companyId}, ${`Phase 4 ${companyId}`}, ${`P${companyId.slice(0, 5)}`}) + `; + await sql` + INSERT INTO "tool_applications" ("id", "company_id", "name", "type") + VALUES (${applicationId}, ${companyId}, ${`App ${applicationId}`}, 'mcp_http') + `; + await sql` + INSERT INTO "tool_connections" ( + "id", "company_id", "application_id", "name", "uid", "transport", + "status", "enabled", "health_status", "credential_policy", "credential_secret_refs" + ) VALUES ( + ${connectionId}, ${companyId}, ${applicationId}, ${`Connection ${connectionId}`}, + ${`connection-${connectionId}`}, 'mcp_remote', 'active', true, 'healthy', + ${input.credentialPolicy ?? "shared"}, + ${sql.json(input.includeConnectionReference ? [{ secretId, version: "latest" }] : [])} + ) + `; + await sql` + INSERT INTO "company_secrets" ("id", "company_id", "key", "name", "scope") + VALUES (${secretId}, ${companyId}, ${`secret-${secretId}`}, ${`Secret ${secretId}`}, 'company') + `; + for (const ownerUserId of input.ownerUserIds) { + await sql` + INSERT INTO "connection_grants" ( + "company_id", "connection_id", "kind", "subject_user_id", "credential_secret_refs" + ) VALUES ( + ${companyId}, ${connectionId}, 'user', ${ownerUserId}, + ${sql.json([{ secretId, version: "latest" }])} + ) + `; + } + if (input.includeOrganizationGrant) { + await sql` + INSERT INTO "connection_grants" ( + "company_id", "connection_id", "kind", "credential_secret_refs" + ) VALUES ( + ${companyId}, ${connectionId}, 'organization', + ${sql.json([{ secretId, version: "latest" }])} + ) + `; + } + if (input.includeCompanyBinding) { + await sql` + INSERT INTO "company_secret_bindings" ( + "company_id", "secret_id", "target_type", "target_id", "config_path" + ) VALUES ( + ${companyId}, ${secretId}, 'environment', ${randomUUID()}, 'env.SHARED_TOKEN' + ) + `; + } + if (input.includeRoutineTrigger) { + const routineId = randomUUID(); + await sql` + INSERT INTO "routines" ("id", "company_id", "title") + VALUES (${routineId}, ${companyId}, ${`Routine ${routineId}`}) + `; + await sql` + INSERT INTO "routine_triggers" ("company_id", "routine_id", "kind", "secret_id") + VALUES (${companyId}, ${routineId}, 'webhook', ${secretId}) + `; + } + return { companyId, connectionId, secretId, ownerUserId: input.ownerUserIds[0]! }; + } + + try { + const valid = await seedLegacyCredential({ ownerUserIds: ["alice"] }); + await rewindMigration(); + await applyPendingMigrations(database.connectionString); + const converted = await sql<{ + scope: string; + owner_user_id: string | null; + user_secret_definition_id: string | null; + }[]>` + SELECT "scope", "owner_user_id", "user_secret_definition_id" + FROM "company_secrets" + WHERE "id" = ${valid.secretId} + `; + expect(converted).toEqual([{ + scope: "user", + owner_user_id: valid.ownerUserId, + user_secret_definition_id: expect.any(String), + }]); + + const bound = await seedLegacyCredential({ + ownerUserIds: ["binding-owner"], + includeConnectionReference: true, + includeCompanyBinding: true, + credentialPolicy: "per_user", + }); + await rewindMigration(); + await applyPendingMigrations(database.connectionString); + expect(await sql<{ scope: string; owner_user_id: string | null }[]>` + SELECT "scope", "owner_user_id" + FROM "company_secrets" + WHERE "id" = ${bound.secretId} + `).toEqual([{ scope: "company", owner_user_id: null }]); + expect(await sql<{ status: string; enabled: boolean; credential_secret_refs: unknown[] }[]>` + SELECT "status", "enabled", "credential_secret_refs" + FROM "tool_connections" + WHERE "id" = ${bound.connectionId} + `).toEqual([{ + status: "active", + enabled: true, + credential_secret_refs: [{ secretId: bound.secretId, version: "latest" }], + }]); + + const routine = await seedLegacyCredential({ + ownerUserIds: ["routine-owner"], + includeConnectionReference: true, + includeRoutineTrigger: true, + credentialPolicy: "per_user", + }); + await rewindMigration(); + await applyPendingMigrations(database.connectionString); + expect(await sql<{ scope: string; owner_user_id: string | null }[]>` + SELECT "scope", "owner_user_id" + FROM "company_secrets" + WHERE "id" = ${routine.secretId} + `).toEqual([{ scope: "company", owner_user_id: null }]); + expect(await sql<{ status: string; enabled: boolean; credential_secret_refs: unknown[] }[]>` + SELECT "status", "enabled", "credential_secret_refs" + FROM "tool_connections" + WHERE "id" = ${routine.connectionId} + `).toEqual([{ + status: "active", + enabled: true, + credential_secret_refs: [{ secretId: routine.secretId, version: "latest" }], + }]); + expect(await sql<{ status: string; credential_secret_refs: unknown[] }[]>` + SELECT "status", "credential_secret_refs" + FROM "connection_grants" + WHERE "company_id" = ${routine.companyId} AND "kind" = 'user' + `).toEqual([{ status: "needs_reauthorization", credential_secret_refs: [] }]); + expect(await sql<{ secret_id: string }[]>` + SELECT "secret_id" + FROM "routine_triggers" + WHERE "company_id" = ${routine.companyId} + `).toEqual([{ secret_id: routine.secretId }]); + expect(await sql<{ status: string; enabled: boolean; credential_secret_refs: unknown[] }[]>` + SELECT "status", "enabled", "credential_secret_refs" + FROM "tool_connections" + WHERE "id" = ${routine.connectionId} + `).toEqual([{ + status: "active", + enabled: true, + credential_secret_refs: [{ secretId: routine.secretId, version: "latest" }], + }]); + + // Replaying also preserves the direct routine-trigger reference. + await rewindMigration(); + await applyPendingMigrations(database.connectionString); + expect(await sql<{ scope: string; owner_user_id: string | null }[]>` + SELECT "scope", "owner_user_id" + FROM "company_secrets" + WHERE "id" = ${routine.secretId} + `).toEqual([{ scope: "company", owner_user_id: null }]); + expect(await sql<{ status: string; credential_secret_refs: unknown[] }[]>` + SELECT "status", "credential_secret_refs" + FROM "connection_grants" + WHERE "company_id" = ${bound.companyId} AND "kind" = 'user' + `).toEqual([{ status: "needs_reauthorization", credential_secret_refs: [] }]); + expect(await sql<{ secret_id: string }[]>` + SELECT "secret_id" + FROM "company_secret_bindings" + WHERE "company_id" = ${bound.companyId} + `).toEqual([{ secret_id: bound.secretId }]); + + // Replaying the migration preserves the same company-scoped binding. + await rewindMigration(); + await applyPendingMigrations(database.connectionString); + expect(await sql<{ scope: string; owner_user_id: string | null }[]>` + SELECT "scope", "owner_user_id" + FROM "company_secrets" + WHERE "id" = ${bound.secretId} + `).toEqual([{ scope: "company", owner_user_id: null }]); + expect(await sql<{ status: string; enabled: boolean; credential_secret_refs: unknown[] }[]>` + SELECT "status", "enabled", "credential_secret_refs" + FROM "tool_connections" + WHERE "id" = ${bound.connectionId} + `).toEqual([{ + status: "active", + enabled: true, + credential_secret_refs: [{ secretId: bound.secretId, version: "latest" }], + }]); + + const ambiguous = await seedLegacyCredential({ ownerUserIds: ["alice", "bob"] }); + await rewindMigration(); + await expect(applyPendingMigrations(database.connectionString)).resolves.toBeUndefined(); + expect(await sql<{ + status: string; + is_default: boolean; + credential_secret_refs: unknown[]; + }[]>` + SELECT "status", "is_default", "credential_secret_refs" + FROM "connection_grants" + WHERE "company_id" = ${ambiguous.companyId} + ORDER BY "is_default" DESC, "id" + `).toEqual([ + { status: "active", is_default: true, credential_secret_refs: [] }, + { status: "needs_reauthorization", is_default: false, credential_secret_refs: [] }, + { status: "needs_reauthorization", is_default: false, credential_secret_refs: [] }, + ]); + expect(await sql<{ scope: string; owner_user_id: string | null }[]>` + SELECT "scope", "owner_user_id" + FROM "company_secrets" + WHERE "id" = ${ambiguous.secretId} + `).toEqual([{ scope: "company", owner_user_id: null }]); + + const mixed = await seedLegacyCredential({ + ownerUserIds: ["carol"], + includeOrganizationGrant: true, + includeConnectionReference: true, + }); + await rewindMigration(); + await expect(applyPendingMigrations(database.connectionString)).resolves.toBeUndefined(); + expect(await sql<{ + status: string; + enabled: boolean; + health_status: string; + credential_secret_refs: unknown[]; + }[]>` + SELECT "status", "enabled", "health_status", "credential_secret_refs" + FROM "tool_connections" + WHERE "id" = ${mixed.connectionId} + `).toEqual([{ + status: "active", + enabled: true, + health_status: "healthy", + credential_secret_refs: [{ secretId: mixed.secretId, version: "latest" }], + }]); + expect(await sql<{ + kind: string; + status: string; + is_default: boolean; + credential_secret_refs: unknown[]; + }[]>` + SELECT "kind", "status", "is_default", "credential_secret_refs" + FROM "connection_grants" + WHERE "company_id" = ${mixed.companyId} + ORDER BY "kind", "is_default" DESC, "id" + `).toEqual([ + { + kind: "organization", + status: "active", + is_default: true, + credential_secret_refs: [{ secretId: mixed.secretId, version: "latest" }], + }, + { + kind: "organization", + status: "active", + is_default: false, + credential_secret_refs: [{ secretId: mixed.secretId, version: "latest" }], + }, + { + kind: "user", + status: "needs_reauthorization", + is_default: false, + credential_secret_refs: [], + }, + ]); + } finally { + await sql.end(); + } + }, 45_000); +}); diff --git a/packages/db/src/connections-v3-schema-core-migration.test.ts b/packages/db/src/connections-v3-schema-core-migration.test.ts index d08b7e16ec..c86257a961 100644 --- a/packages/db/src/connections-v3-schema-core-migration.test.ts +++ b/packages/db/src/connections-v3-schema-core-migration.test.ts @@ -25,6 +25,8 @@ describeEmbeddedPostgres("connections v3 schema core migration", () => { cleanups.push(async () => sql.end()); await sql`DELETE FROM "drizzle"."__drizzle_migrations" WHERE "hash" = ${await migrationHash()}`; + await sql`DROP TABLE IF EXISTS "connection_grant_delegations"`; + await sql`DROP TABLE IF EXISTS "connection_grant_members"`; await sql`DROP TABLE IF EXISTS "connection_grants"`; await sql`DROP INDEX IF EXISTS "tool_connections_company_uid_uq"`; await sql`ALTER TABLE "tool_connections" DROP CONSTRAINT IF EXISTS "tool_connections_company_id_uq"`; @@ -71,6 +73,7 @@ describeEmbeddedPostgres("connections v3 schema core migration", () => { VALUES (${companyId}, ${connectionId}, 'user', 'user-1', true) `).rejects.toMatchObject({ code: "23514" }); + await sql`DROP TABLE IF EXISTS "connection_grant_members"`; await sql`DROP TABLE "connection_grants"`; await sql`DROP INDEX "tool_connections_company_uid_uq"`; await sql`ALTER TABLE "tool_connections" DROP CONSTRAINT "tool_connections_company_id_uq"`; diff --git a/packages/db/src/migrations/0232_fixed_hannibal_king.sql b/packages/db/src/migrations/0232_fixed_hannibal_king.sql new file mode 100644 index 0000000000..0cec443d15 --- /dev/null +++ b/packages/db/src/migrations/0232_fixed_hannibal_king.sql @@ -0,0 +1,202 @@ +CREATE TABLE IF NOT EXISTS "connection_grant_members" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "grant_id" uuid NOT NULL, + "subject_type" text NOT NULL, + "subject_id" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "connection_grant_members_subject_type_check" CHECK ("connection_grant_members"."subject_type" in ('user')) +); +--> statement-breakpoint +ALTER TABLE "connection_grant_members" DROP CONSTRAINT IF EXISTS "connection_grant_members_company_grant_fk";--> statement-breakpoint +ALTER TABLE "connection_grant_members" DROP CONSTRAINT IF EXISTS "connection_grant_members_grant_id_connection_grants_id_fk";--> statement-breakpoint +ALTER TABLE "connection_grant_members" DROP CONSTRAINT IF EXISTS "connection_grant_members_company_id_companies_id_fk";--> statement-breakpoint +ALTER TABLE "connection_grants" DROP CONSTRAINT IF EXISTS "connection_grants_kind_check";--> statement-breakpoint +ALTER TABLE "connection_grants" DROP CONSTRAINT IF EXISTS "connection_grants_subject_check";--> statement-breakpoint +ALTER TABLE "connection_grants" DROP CONSTRAINT IF EXISTS "connection_grants_default_check";--> statement-breakpoint +ALTER TABLE "tool_connections" DROP CONSTRAINT IF EXISTS "tool_connections_credential_policy_check";--> statement-breakpoint +DROP INDEX IF EXISTS "connection_grants_default_uq";--> statement-breakpoint +UPDATE "connection_grants" SET "kind" = 'organization' WHERE "kind" = 'workspace';--> statement-breakpoint +INSERT INTO "connection_grants" ( + "company_id", "connection_id", "kind", "credential_secret_refs", "status", "is_default", + "created_by_agent_id", "created_by_user_id", "created_at", "updated_at" +) +SELECT + c."company_id", c."id", 'organization', c."credential_secret_refs", 'active', true, + c."created_by_agent_id", c."created_by_user_id", c."created_at", c."updated_at" +FROM "tool_connections" c +WHERE NOT EXISTS ( + SELECT 1 FROM "connection_grants" g + WHERE g."connection_id" = c."id" AND g."is_default" = true +);--> statement-breakpoint +ALTER TABLE "tool_connections" ADD COLUMN IF NOT EXISTS "credential_policy" text DEFAULT 'shared' NOT NULL;--> statement-breakpoint +ALTER TABLE "connection_grant_members" ADD CONSTRAINT "connection_grant_members_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'connection_grants_company_id_uq' + AND conrelid = 'connection_grants'::regclass + ) THEN + ALTER TABLE "connection_grants" ADD CONSTRAINT "connection_grants_company_id_uq" UNIQUE("company_id","id"); + END IF; +END $$;--> statement-breakpoint +ALTER TABLE "connection_grant_members" ADD CONSTRAINT "connection_grant_members_company_grant_fk" FOREIGN KEY ("company_id","grant_id") REFERENCES "public"."connection_grants"("company_id","id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "connection_grant_members_company_subject_idx" ON "connection_grant_members" USING btree ("company_id","subject_type","subject_id");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "connection_grant_members_grant_subject_uq" ON "connection_grant_members" USING btree ("grant_id","subject_type","subject_id");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "connection_grants_default_uq" ON "connection_grants" USING btree ("connection_id") WHERE "connection_grants"."is_default" = true and "connection_grants"."kind" = 'organization';--> statement-breakpoint +ALTER TABLE "connection_grants" ADD CONSTRAINT "connection_grants_kind_check" CHECK ("connection_grants"."kind" in ('organization', 'user'));--> statement-breakpoint +ALTER TABLE "connection_grants" ADD CONSTRAINT "connection_grants_subject_check" CHECK (("connection_grants"."kind" = 'user' and "connection_grants"."subject_user_id" is not null) or ("connection_grants"."kind" = 'organization' and "connection_grants"."subject_user_id" is null));--> statement-breakpoint +ALTER TABLE "connection_grants" ADD CONSTRAINT "connection_grants_default_check" CHECK ("connection_grants"."is_default" = false or "connection_grants"."kind" = 'organization');--> statement-breakpoint +ALTER TABLE "tool_connections" ADD CONSTRAINT "tool_connections_credential_policy_check" CHECK ("tool_connections"."credential_policy" in ('shared', 'per_user', 'per_user_with_fallback')); +--> statement-breakpoint +ALTER TABLE "issue_thread_interactions" ADD COLUMN IF NOT EXISTS "addressee_user_id" text;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "issue_thread_interactions_addressee_user_idx" ON "issue_thread_interactions" USING btree ("addressee_user_id"); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "connection_grant_delegations" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "grant_id" uuid NOT NULL, + "agent_id" uuid NOT NULL, + "created_by_user_id" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "connection_grant_delegations" DROP CONSTRAINT IF EXISTS "connection_grant_delegations_company_id_companies_id_fk"; +--> statement-breakpoint +ALTER TABLE "connection_grant_delegations" ADD CONSTRAINT "connection_grant_delegations_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "connection_grant_delegations" DROP CONSTRAINT IF EXISTS "connection_grant_delegations_agent_id_agents_id_fk"; +--> statement-breakpoint +ALTER TABLE "connection_grant_delegations" ADD CONSTRAINT "connection_grant_delegations_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "connection_grant_delegations" DROP CONSTRAINT IF EXISTS "connection_grant_delegations_company_grant_fk"; +--> statement-breakpoint +ALTER TABLE "connection_grant_delegations" ADD CONSTRAINT "connection_grant_delegations_company_grant_fk" FOREIGN KEY ("company_id","grant_id") REFERENCES "public"."connection_grants"("company_id","id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "connection_grant_delegations_company_agent_idx" ON "connection_grant_delegations" USING btree ("company_id","agent_id");--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "connection_grant_delegations_grant_agent_uq" ON "connection_grant_delegations" USING btree ("grant_id","agent_id"); +--> statement-breakpoint +-- A legacy company-scoped credential can only become user-scoped when exactly +-- one personal owner references it and no organization grant, connection, or +-- company binding also references it. Ambiguous rows fail closed in-place: +-- remove only personal-grant references and require those users to +-- reauthorize. Organization grants, shared/fallback connections, company +-- bindings, and routine triggers keep the original company-scoped secret so +-- their existing consumers continue to resolve it. Connection rows also keep +-- direct references: policy-aware resolution ignores them for strict per-user +-- access, while stripping them would break an independent shared consumer. +DROP TABLE IF EXISTS "phase4_ambiguous_personal_secrets"; +--> statement-breakpoint +CREATE TEMP TABLE "phase4_ambiguous_personal_secrets" ON COMMIT DROP AS +WITH personal_secret_owners AS ( + SELECT + s."id" AS "secret_id", + s."company_id", + count(DISTINCT g."subject_user_id") AS "owner_count" + FROM "company_secrets" s + JOIN "connection_grants" g + ON g."company_id" = s."company_id" + AND g."kind" = 'user' + CROSS JOIN LATERAL jsonb_array_elements(g."credential_secret_refs") personal_ref + WHERE s."scope" = 'company' + AND s."id"::text = personal_ref ->> 'secretId' + GROUP BY s."id", s."company_id" +) + SELECT owners."secret_id", owners."company_id" + FROM personal_secret_owners owners + WHERE owners."owner_count" <> 1 + OR EXISTS ( + SELECT 1 + FROM "connection_grants" organization_grant + CROSS JOIN LATERAL jsonb_array_elements(organization_grant."credential_secret_refs") organization_ref + WHERE organization_grant."company_id" = owners."company_id" + AND organization_grant."kind" <> 'user' + AND organization_ref ->> 'secretId' = owners."secret_id"::text + ) + OR EXISTS ( + SELECT 1 + FROM "tool_connections" connection + CROSS JOIN LATERAL jsonb_array_elements(connection."credential_secret_refs") connection_ref + WHERE connection."company_id" = owners."company_id" + AND connection_ref ->> 'secretId' = owners."secret_id"::text + ) + OR EXISTS ( + SELECT 1 + FROM "company_secret_bindings" binding + WHERE binding."company_id" = owners."company_id" + AND binding."secret_id" = owners."secret_id" + ) + OR EXISTS ( + SELECT 1 + FROM "routine_triggers" routine_trigger + WHERE routine_trigger."company_id" = owners."company_id" + AND routine_trigger."secret_id" = owners."secret_id" + ); +--> statement-breakpoint +WITH ambiguous_secrets AS ( + SELECT "secret_id", "company_id" FROM "phase4_ambiguous_personal_secrets" +) +UPDATE "connection_grants" grant_row +SET + "credential_secret_refs" = COALESCE(( + SELECT jsonb_agg(ref) + FROM jsonb_array_elements(grant_row."credential_secret_refs") ref + WHERE NOT EXISTS ( + SELECT 1 + FROM ambiguous_secrets ambiguous + WHERE ambiguous."company_id" = grant_row."company_id" + AND ref ->> 'secretId' = ambiguous."secret_id"::text + ) + ), '[]'::jsonb), + "status" = 'needs_reauthorization', + "is_default" = false, + "updated_at" = now() +WHERE grant_row."kind" = 'user' + AND EXISTS ( + SELECT 1 + FROM jsonb_array_elements(grant_row."credential_secret_refs") ref + JOIN ambiguous_secrets ambiguous + ON ambiguous."company_id" = grant_row."company_id" + AND ref ->> 'secretId' = ambiguous."secret_id"::text +); +--> statement-breakpoint +DROP TABLE IF EXISTS "phase4_ambiguous_personal_secrets"; +--> statement-breakpoint +INSERT INTO "user_secret_definitions" ( + "company_id", "key", "name", "description", "provider", "managed_mode", + "provider_config_id", "provider_metadata", "created_by_agent_id", "created_by_user_id" +) +SELECT DISTINCT + s."company_id", + 'tool_oauth.' || s."id"::text, + s."name", + 'Personal connection credential migrated to user scope.', + s."provider", + s."managed_mode", + s."provider_config_id", + s."provider_metadata", + s."created_by_agent_id", + s."created_by_user_id" +FROM "company_secrets" s +JOIN "connection_grants" g ON g."company_id" = s."company_id" AND g."kind" = 'user' +CROSS JOIN LATERAL jsonb_array_elements(g."credential_secret_refs") ref +WHERE s."id"::text = ref ->> 'secretId' + AND s."scope" = 'company' +ON CONFLICT DO NOTHING; +--> statement-breakpoint +UPDATE "company_secrets" s +SET + "scope" = 'user', + "owner_user_id" = owner_map."owner_user_id", + "user_secret_definition_id" = d."id", + "updated_at" = now() +FROM ( + SELECT s2."id" AS "secret_id", min(g."subject_user_id") AS "owner_user_id" + FROM "company_secrets" s2 + JOIN "connection_grants" g ON g."company_id" = s2."company_id" AND g."kind" = 'user' + CROSS JOIN LATERAL jsonb_array_elements(g."credential_secret_refs") ref + WHERE s2."id"::text = ref ->> 'secretId' AND s2."scope" = 'company' + GROUP BY s2."id" + HAVING count(DISTINCT g."subject_user_id") = 1 +) owner_map +JOIN "user_secret_definitions" d ON d."key" = 'tool_oauth.' || owner_map."secret_id"::text AND d."deleted_at" IS NULL +WHERE s."id" = owner_map."secret_id" AND d."company_id" = s."company_id"; diff --git a/packages/db/src/migrations/meta/0232_snapshot.json b/packages/db/src/migrations/meta/0232_snapshot.json new file mode 100644 index 0000000000..ef3d5906e4 --- /dev/null +++ b/packages/db/src/migrations/meta/0232_snapshot.json @@ -0,0 +1,41904 @@ +{ + "id": "c9e4f60e-edb0-49a8-a654-4907293fef22", + "prevId": "636fa88c-efae-4ad7-b095-a93334b8657e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_agent_created_idx": { + "name": "activity_log_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_responsible_user_created_idx": { + "name": "activity_log_company_responsible_user_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.adapter_auth_sessions": { + "name": "adapter_auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_session_id": { + "name": "public_session_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "promotion_expires_at": { + "name": "promotion_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "bound_at": { + "name": "bound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "adapter_auth_sessions_company_status_idx": { + "name": "adapter_auth_sessions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_company_owner_adapter_active_uq": { + "name": "adapter_auth_sessions_company_owner_adapter_active_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"adapter_auth_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'promoting', 'awaiting_code', 'submitting')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_public_session_id_uq": { + "name": "adapter_auth_sessions_public_session_id_uq", + "columns": [ + { + "expression": "public_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_environment_idx": { + "name": "adapter_auth_sessions_environment_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_expires_idx": { + "name": "adapter_auth_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_provider_lease_idx": { + "name": "adapter_auth_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "adapter_auth_sessions_company_id_companies_id_fk": { + "name": "adapter_auth_sessions_company_id_companies_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "adapter_auth_sessions_environment_id_environments_id_fk": { + "name": "adapter_auth_sessions_environment_id_environments_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_config": { + "name": "scope_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_memberships": { + "name": "agent_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_memberships_company_user_idx": { + "name": "agent_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_starred_idx": { + "name": "agent_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_agent_idx": { + "name": "agent_memberships_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_agent_uq": { + "name": "agent_memberships_company_user_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memberships_company_id_companies_id_fk": { + "name": "agent_memberships_company_id_companies_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memberships_agent_id_agents_id_fk": { + "name": "agent_memberships_agent_id_agents_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_review_path_recovery_idempotency_uq": { + "name": "agent_wakeup_requests_review_path_recovery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_review_path_lost:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_disposition_repair_idempotency_uq": { + "name": "agent_wakeup_requests_disposition_repair_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_disposition_repair:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_question_response_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_question_response_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_payload_issue_idx": { + "name": "agent_wakeup_requests_company_payload_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"payload\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_reason": { + "name": "error_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_default_environment_idx": { + "name": "agents_company_default_environment_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "default_environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_default_environment_id_environments_id_fk": { + "name": "agents_default_environment_id_environments_id_fk", + "tableFrom": "agents", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "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 + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_issuer_account_id_uq": { + "name": "account_issuer_account_id_uq", + "columns": [ + { + "expression": "issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_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 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.built_in_managed_resources": { + "name": "built_in_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_key": { + "name": "bundle_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stock_version": { + "name": "stock_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stock_hash": { + "name": "stock_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "built_in_managed_resources_company_idx": { + "name": "built_in_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_resource_idx": { + "name": "built_in_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_company_bundle_resource_uq": { + "name": "built_in_managed_resources_company_bundle_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bundle_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "built_in_managed_resources_company_id_companies_id_fk": { + "name": "built_in_managed_resources_company_id_companies_id_fk", + "tableFrom": "built_in_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_attachments": { + "name": "case_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_attachments_company_case_idx": { + "name": "case_attachments_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_attachments_asset_uq": { + "name": "case_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_attachments_company_id_companies_id_fk": { + "name": "case_attachments_company_id_companies_id_fk", + "tableFrom": "case_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_case_id_cases_id_fk": { + "name": "case_attachments_case_id_cases_id_fk", + "tableFrom": "case_attachments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_asset_id_assets_id_fk": { + "name": "case_attachments_asset_id_assets_id_fk", + "tableFrom": "case_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_documents": { + "name": "case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_documents_company_case_key_uq": { + "name": "case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_document_uq": { + "name": "case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_company_case_updated_idx": { + "name": "case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_documents_company_id_companies_id_fk": { + "name": "case_documents_company_id_companies_id_fk", + "tableFrom": "case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_case_id_cases_id_fk": { + "name": "case_documents_case_id_cases_id_fk", + "tableFrom": "case_documents", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_document_id_documents_id_fk": { + "name": "case_documents_document_id_documents_id_fk", + "tableFrom": "case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_events": { + "name": "case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_events_case_created_idx": { + "name": "case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_events_company_case_idx": { + "name": "case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_events_company_id_companies_id_fk": { + "name": "case_events_company_id_companies_id_fk", + "tableFrom": "case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_case_id_cases_id_fk": { + "name": "case_events_case_id_cases_id_fk", + "tableFrom": "case_events", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_actor_agent_id_agents_id_fk": { + "name": "case_events_actor_agent_id_agents_id_fk", + "tableFrom": "case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_events_kind_check": { + "name": "case_events_kind_check", + "value": "\"case_events\".\"kind\" in (\n 'created',\n 'updated',\n 'fields_changed',\n 'status_changed',\n 'issue_linked',\n 'issue_unlinked',\n 'document_revised',\n 'child_linked',\n 'attachment_added',\n 'label_added',\n 'label_removed'\n )" + }, + "case_events_actor_type_check": { + "name": "case_events_actor_type_check", + "value": "\"case_events\".\"actor_type\" in ('user', 'agent', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.case_issue_links": { + "name": "case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_issue_links_case_issue_uq": { + "name": "case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_company_case_idx": { + "name": "case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_issue_idx": { + "name": "case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_issue_links_company_id_companies_id_fk": { + "name": "case_issue_links_company_id_companies_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_case_id_cases_id_fk": { + "name": "case_issue_links_case_id_cases_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_issue_id_issues_id_fk": { + "name": "case_issue_links_issue_id_issues_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_issue_links_role_check": { + "name": "case_issue_links_role_check", + "value": "\"case_issue_links\".\"role\" in ('origin', 'work', 'reference')" + } + }, + "isRLSEnabled": false + }, + "public.case_labels": { + "name": "case_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_labels_case_label_uq": { + "name": "case_labels_case_label_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_company_case_idx": { + "name": "case_labels_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_label_idx": { + "name": "case_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_labels_company_id_companies_id_fk": { + "name": "case_labels_company_id_companies_id_fk", + "tableFrom": "case_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_case_id_cases_id_fk": { + "name": "case_labels_case_id_cases_id_fk", + "tableFrom": "case_labels", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_label_id_labels_id_fk": { + "name": "case_labels_label_id_labels_id_fk", + "tableFrom": "case_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cases": { + "name": "cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_number": { + "name": "case_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "case_type": { + "name": "case_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cases_company_case_number_uq": { + "name": "cases_company_case_number_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_identifier_uq": { + "name": "cases_identifier_uq", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_key_uq": { + "name": "cases_company_type_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_status_idx": { + "name": "cases_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_idx": { + "name": "cases_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_project_idx": { + "name": "cases_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_parent_idx": { + "name": "cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_title_search_idx": { + "name": "cases_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_identifier_search_idx": { + "name": "cases_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_summary_search_idx": { + "name": "cases_summary_search_idx", + "columns": [ + { + "expression": "summary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "cases_company_id_companies_id_fk": { + "name": "cases_company_id_companies_id_fk", + "tableFrom": "cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cases_project_id_projects_id_fk": { + "name": "cases_project_id_projects_id_fk", + "tableFrom": "cases", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_parent_case_id_cases_id_fk": { + "name": "cases_parent_case_id_cases_id_fk", + "tableFrom": "cases", + "tableTo": "cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_created_by_agent_id_agents_id_fk": { + "name": "cases_created_by_agent_id_agents_id_fk", + "tableFrom": "cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cases_status_check": { + "name": "cases_status_check", + "value": "\"cases\".\"status\" in ('draft', 'in_progress', 'in_review', 'approved', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "default_responsible_user_id": { + "name": "default_responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interaction_resolver_governance": { + "name": "interaction_resolver_governance", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_onboarding_seeds": { + "name": "company_onboarding_seeds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mission": { + "name": "mission", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_role": { + "name": "agent_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_title": { + "name": "first_task_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_details": { + "name": "first_task_details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_onboarding_seeds_company_uq": { + "name": "company_onboarding_seeds_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_onboarding_seeds_company_id_companies_id_fk": { + "name": "company_onboarding_seeds_company_id_companies_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_onboarding_seeds_goal_id_goals_id_fk": { + "name": "company_onboarding_seeds_goal_id_goals_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_agent_id_agents_id_fk": { + "name": "company_onboarding_seeds_agent_id_agents_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_issue_id_issues_id_fk": { + "name": "company_onboarding_seeds_issue_id_issues_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_bindings": { + "name": "company_secret_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "projection_allowlist_key": { + "name": "projection_allowlist_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_bindings_company_idx": { + "name": "company_secret_bindings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_secret_idx": { + "name": "company_secret_bindings_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_idx": { + "name": "company_secret_bindings_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_path_uq": { + "name": "company_secret_bindings_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_bindings_company_id_companies_id_fk": { + "name": "company_secret_bindings_company_id_companies_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_bindings_secret_id_company_secrets_id_fk": { + "name": "company_secret_bindings_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_proposals": { + "name": "company_secret_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "proposed_name": { + "name": "proposed_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_key": { + "name": "proposed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_description": { + "name": "proposed_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "justification": { + "name": "justification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_ciphertext": { + "name": "value_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "value_fingerprint_sha256": { + "name": "value_fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_length": { + "name": "value_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_proposal_id": { + "name": "secret_proposal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "binding_target_policy_snapshot": { + "name": "binding_target_policy_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposer_ancestor_ids_snapshot": { + "name": "proposer_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_ancestor_ids_snapshot": { + "name": "target_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proposed_by_agent_id": { + "name": "proposed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolution_reason": { + "name": "resolution_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_secret_id": { + "name": "created_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_binding_config_path": { + "name": "applied_binding_config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ciphertext_scrubbed_at": { + "name": "ciphertext_scrubbed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_proposals_company_status_idx": { + "name": "company_secret_proposals_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_proposer_status_idx": { + "name": "company_secret_proposals_proposer_status_idx", + "columns": [ + { + "expression": "proposed_by_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_expiry_idx": { + "name": "company_secret_proposals_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_secret_proposal_idx": { + "name": "company_secret_proposals_secret_proposal_idx", + "columns": [ + { + "expression": "secret_proposal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_interaction_idx": { + "name": "company_secret_proposals_interaction_idx", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_proposals_company_id_companies_id_fk": { + "name": "company_secret_proposals_company_id_companies_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk": { + "name": "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secret_proposals", + "columnsFrom": [ + "secret_proposal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_target_id_agents_id_fk": { + "name": "company_secret_proposals_target_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_proposed_by_agent_id_agents_id_fk": { + "name": "company_secret_proposals_proposed_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "proposed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_issue_id_issues_id_fk": { + "name": "company_secret_proposals_origin_issue_id_issues_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk": { + "name": "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk": { + "name": "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_created_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_created_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "created_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secret_proposals_kind_check": { + "name": "company_secret_proposals_kind_check", + "value": "\"company_secret_proposals\".\"kind\" in ('secret', 'binding')" + }, + "company_secret_proposals_status_check": { + "name": "company_secret_proposals_status_check", + "value": "\"company_secret_proposals\".\"status\" in ('pending', 'approved', 'rejected', 'withdrawn', 'expired')" + }, + "company_secret_proposals_projection_check": { + "name": "company_secret_proposals_projection_check", + "value": "\"company_secret_proposals\".\"projection_class\" = 'unclassified'" + }, + "company_secret_proposals_shape_check": { + "name": "company_secret_proposals_shape_check", + "value": "(\n \"company_secret_proposals\".\"kind\" = 'secret'\n and \"company_secret_proposals\".\"proposed_name\" is not null\n and \"company_secret_proposals\".\"proposed_key\" is not null\n and \"company_secret_proposals\".\"secret_id\" is null\n and \"company_secret_proposals\".\"secret_proposal_id\" is null\n and \"company_secret_proposals\".\"target_type\" is null\n and \"company_secret_proposals\".\"target_id\" is null\n and \"company_secret_proposals\".\"config_path\" is null\n ) or (\n \"company_secret_proposals\".\"kind\" = 'binding'\n and ((\"company_secret_proposals\".\"secret_id\" is not null)::int + (\"company_secret_proposals\".\"secret_proposal_id\" is not null)::int) = 1\n and \"company_secret_proposals\".\"target_type\" = 'agent'\n and \"company_secret_proposals\".\"target_id\" is not null\n and \"company_secret_proposals\".\"config_path\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_secret_provider_configs": { + "name": "company_secret_provider_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_details": { + "name": "health_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_provider_configs_company_idx": { + "name": "company_secret_provider_configs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_company_provider_idx": { + "name": "company_secret_provider_configs_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_default_uq": { + "name": "company_secret_provider_configs_default_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secret_provider_configs\".\"is_default\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_provider_configs_company_id_companies_id_fk": { + "name": "company_secret_provider_configs_company_id_companies_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_provider_configs_created_by_agent_id_agents_id_fk": { + "name": "company_secret_provider_configs_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_version_ref": { + "name": "provider_version_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'current'" + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rotation_job_id": { + "name": "rotation_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_fingerprint_idx": { + "name": "company_secret_versions_fingerprint_idx", + "columns": [ + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_scope_idx": { + "name": "company_secrets_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_owner_idx": { + "name": "company_secrets_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_idx": { + "name": "company_secrets_user_definition_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_provider_config_idx": { + "name": "company_secrets_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_key_uq": { + "name": "company_secrets_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_uq": { + "name": "company_secrets_user_definition_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'user' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "company_secrets", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "company_secrets_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "company_secrets", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secrets_scope_shape_check": { + "name": "company_secrets_scope_shape_check", + "value": "(\n \"company_secrets\".\"scope\" = 'company'\n and \"company_secrets\".\"owner_user_id\" is null\n and \"company_secrets\".\"user_secret_definition_id\" is null\n ) or (\n \"company_secrets\".\"scope\" = 'user'\n and \"company_secrets\".\"owner_user_id\" is not null\n and \"company_secrets\".\"user_secret_definition_id\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_skill_policies": { + "name": "company_skill_policies", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "default_effect": { + "name": "default_effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rules": { + "name": "rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_skill_policies_company_id_companies_id_fk": { + "name": "company_skill_policies_company_id_companies_id_fk", + "tableFrom": "company_skill_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_comments": { + "name": "company_skill_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_comment_id": { + "name": "parent_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_comments_company_skill_created_idx": { + "name": "company_skill_comments_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_comments_parent_idx": { + "name": "company_skill_comments_parent_idx", + "columns": [ + { + "expression": "parent_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_comments_company_id_companies_id_fk": { + "name": "company_skill_comments_company_id_companies_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_company_skill_id_company_skills_id_fk": { + "name": "company_skill_comments_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_parent_comment_id_company_skill_comments_id_fk": { + "name": "company_skill_comments_parent_comment_id_company_skill_comments_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skill_comments", + "columnsFrom": [ + "parent_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_comments_author_agent_id_agents_id_fk": { + "name": "company_skill_comments_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_stars": { + "name": "company_skill_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_stars_skill_agent_idx": { + "name": "company_skill_stars_skill_agent_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_skill_user_idx": { + "name": "company_skill_stars_skill_user_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_company_skill_created_idx": { + "name": "company_skill_stars_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_stars_company_id_companies_id_fk": { + "name": "company_skill_stars_company_id_companies_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_company_skill_id_company_skills_id_fk": { + "name": "company_skill_stars_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_agent_id_agents_id_fk": { + "name": "company_skill_stars_agent_id_agents_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_inputs": { + "name": "company_skill_test_inputs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_inputs_company_skill_name_idx": { + "name": "company_skill_test_inputs_company_skill_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_inputs_company_skill_active_idx": { + "name": "company_skill_test_inputs_company_skill_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_inputs_company_id_companies_id_fk": { + "name": "company_skill_test_inputs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_inputs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_inputs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_run_templates": { + "name": "company_skill_test_run_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_run_templates_company_active_idx": { + "name": "company_skill_test_run_templates_company_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_run_templates_company_id_companies_id_fk": { + "name": "company_skill_test_run_templates_company_id_companies_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_created_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_runs": { + "name": "company_skill_test_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "input_id": { + "name": "input_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "input_snapshot": { + "name": "input_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "skill_version_id": { + "name": "skill_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_config_snapshot": { + "name": "agent_config_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_name": { + "name": "template_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_body": { + "name": "template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rendered_template_body": { + "name": "rendered_template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "harness_issue_description": { + "name": "harness_issue_description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "output_document_key": { + "name": "output_document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'output'" + }, + "output_snapshot": { + "name": "output_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_expires_at": { + "name": "harness_issue_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_deleted_at": { + "name": "harness_issue_deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_runs_company_skill_created_idx": { + "name": "company_skill_test_runs_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_issue_idx": { + "name": "company_skill_test_runs_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_input_created_idx": { + "name": "company_skill_test_runs_company_input_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_status_idx": { + "name": "company_skill_test_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_harness_expires_idx": { + "name": "company_skill_test_runs_company_harness_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_issue_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_runs_company_id_companies_id_fk": { + "name": "company_skill_test_runs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_runs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk": { + "name": "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_test_inputs", + "columnsFrom": [ + "input_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk": { + "name": "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "skill_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_agent_id_agents_id_fk": { + "name": "company_skill_test_runs_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_issue_id_issues_id_fk": { + "name": "company_skill_test_runs_issue_id_issues_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_versions": { + "name": "company_skill_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_id": { + "name": "release_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_name": { + "name": "release_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_versions_skill_revision_idx": { + "name": "company_skill_versions_skill_revision_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_skill_release_idx": { + "name": "company_skill_versions_skill_release_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_skill_versions\".\"release_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_company_skill_created_idx": { + "name": "company_skill_versions_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_versions_company_id_companies_id_fk": { + "name": "company_skill_versions_company_id_companies_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_company_skill_id_company_skills_id_fk": { + "name": "company_skill_versions_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_author_agent_id_agents_id_fk": { + "name": "company_skill_versions_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "homepage_url": { + "name": "homepage_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "sharing_scope": { + "name": "sharing_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "public_share_token": { + "name": "public_share_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "forked_from_skill_id": { + "name": "forked_from_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "forked_from_company_id": { + "name": "forked_from_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "star_count": { + "name": "star_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "install_count": { + "name": "install_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fork_count": { + "name": "fork_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_version_id": { + "name": "current_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_folder_idx": { + "name": "company_skills_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_categories_idx": { + "name": "company_skills_company_categories_idx", + "columns": [ + { + "expression": "categories", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "company_skills_company_sharing_scope_idx": { + "name": "company_skills_company_sharing_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sharing_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_current_version_idx": { + "name": "company_skills_company_current_version_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_forked_from_idx": { + "name": "company_skills_company_forked_from_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "forked_from_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_skills_folder_id_folders_id_fk": { + "name": "company_skills_folder_id_folders_id_fk", + "tableFrom": "company_skills", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_skill_id_company_skills_id_fk": { + "name": "company_skills_forked_from_skill_id_company_skills_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skills", + "columnsFrom": [ + "forked_from_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_company_id_companies_id_fk": { + "name": "company_skills_forked_from_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "forked_from_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_current_version_id_company_skill_versions_id_fk": { + "name": "company_skills_current_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "current_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_transfer_runs": { + "name": "company_transfer_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "actor_key": { + "name": "actor_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "container_ref": { + "name": "container_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest_sha256": { + "name": "manifest_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "blob_count": { + "name": "blob_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_parts": { + "name": "completed_parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_transfer_runs_company_idx": { + "name": "company_transfer_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_idempotency_direction_idx": { + "name": "company_transfer_runs_idempotency_direction_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_actor_status_idx": { + "name": "company_transfer_runs_actor_status_idx", + "columns": [ + { + "expression": "actor_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_transfer_runs_company_id_companies_id_fk": { + "name": "company_transfer_runs_company_id_companies_id_fk", + "tableFrom": "company_transfer_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_user_sidebar_preferences": { + "name": "company_user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_order": { + "name": "project_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_user_sidebar_preferences_company_idx": { + "name": "company_user_sidebar_preferences_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_user_idx": { + "name": "company_user_sidebar_preferences_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_company_user_uq": { + "name": "company_user_sidebar_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_user_sidebar_preferences_company_id_companies_id_fk": { + "name": "company_user_sidebar_preferences_company_id_companies_id_fk", + "tableFrom": "company_user_sidebar_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.completion_contracts": { + "name": "completion_contracts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_authority": { + "name": "completion_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "incomplete_criteria_policy": { + "name": "incomplete_criteria_policy", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contract_json": { + "name": "contract_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "supersedes_contract_id": { + "name": "supersedes_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_contracts_issue_revision_uq": { + "name": "completion_contracts_issue_revision_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "completion_contracts_issue_hash_uq": { + "name": "completion_contracts_issue_hash_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "completion_contracts_company_id_companies_id_fk": { + "name": "completion_contracts_company_id_companies_id_fk", + "tableFrom": "completion_contracts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_issue_company_fk": { + "name": "completion_contracts_issue_company_fk", + "tableFrom": "completion_contracts", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_supersedes_owner_fk": { + "name": "completion_contracts_supersedes_owner_fk", + "tableFrom": "completion_contracts", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "completion_contracts_company_issue_id_uq": { + "name": "completion_contracts_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "cost_status": { + "name": "cost_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reported'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_archive_notification_outbox": { + "name": "decision_archive_notification_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_archive_notification_outbox_uq": { + "name": "decision_archive_notification_outbox_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archive_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_archive_notification_outbox_pending_idx": { + "name": "decision_archive_notification_outbox_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_archive_notification_outbox_company_id_companies_id_fk": { + "name": "decision_archive_notification_outbox_company_id_companies_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_archive_notification_outbox_origin_agent_id_agents_id_fk": { + "name": "decision_archive_notification_outbox_origin_agent_id_agents_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_archive_notification_outbox_status_check": { + "name": "decision_archive_notification_outbox_status_check", + "value": "\"decision_archive_notification_outbox\".\"status\" IN ('pending', 'delivering', 'delivered')" + } + }, + "isRLSEnabled": false + }, + "public.decision_queue_items": { + "name": "decision_queue_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_type": { + "name": "added_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_agent_id": { + "name": "added_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_user_id": { + "name": "added_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by_run_id": { + "name": "added_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_agent_api_key_id": { + "name": "added_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queue_items_queue_source_uq": { + "name": "decision_queue_items_queue_source_uq", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queue_items_company_source_idx": { + "name": "decision_queue_items_company_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queue_items_company_id_companies_id_fk": { + "name": "decision_queue_items_company_id_companies_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_id_agents_id_fk": { + "name": "decision_queue_items_added_by_agent_id_agents_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agents", + "columnsFrom": [ + "added_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "added_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "added_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_queue_company_fk": { + "name": "decision_queue_items_queue_company_fk", + "tableFrom": "decision_queue_items", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id", + "company_id" + ], + "columnsTo": [ + "id", + "company_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_queue_items_actor_check": { + "name": "decision_queue_items_actor_check", + "value": "(\n (\"decision_queue_items\".\"added_by_type\" = 'agent' AND \"decision_queue_items\".\"added_by_agent_id\" IS NOT NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'user' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NOT NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'system' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_queues": { + "name": "decision_queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_type": { + "name": "created_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_api_key_id": { + "name": "created_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retention_days": { + "name": "retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seed_rules": { + "name": "seed_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "seed_rules_enabled": { + "name": "seed_rules_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queues_company_key_uq": { + "name": "decision_queues_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queues_company_updated_idx": { + "name": "decision_queues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queues_company_id_companies_id_fk": { + "name": "decision_queues_company_id_companies_id_fk", + "tableFrom": "decision_queues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_id_agents_id_fk": { + "name": "decision_queues_created_by_agent_id_agents_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queues_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "created_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "decision_queues_id_company_uq": { + "name": "decision_queues_id_company_uq", + "nullsNotDistinct": false, + "columns": [ + "id", + "company_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "decision_queues_creator_check": { + "name": "decision_queues_creator_check", + "value": "(\n (\"decision_queues\".\"created_by_type\" = 'agent' AND \"decision_queues\".\"created_by_agent_id\" IS NOT NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'user' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NOT NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'system' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n )" + }, + "decision_queues_retention_days_check": { + "name": "decision_queues_retention_days_check", + "value": "\"decision_queues\".\"retention_days\" IS NULL OR (\"decision_queues\".\"retention_days\" >= 1 AND \"decision_queues\".\"retention_days\" <= 3650)" + } + }, + "isRLSEnabled": false + }, + "public.decision_retention": { + "name": "decision_retention", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_activity_at": { + "name": "source_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "keep": { + "name": "keep", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_reason": { + "name": "archived_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_type": { + "name": "archived_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_retention_company_source_uq": { + "name": "decision_retention_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_retention_company_archived_idx": { + "name": "decision_retention_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_retention_company_id_companies_id_fk": { + "name": "decision_retention_company_id_companies_id_fk", + "tableFrom": "decision_retention", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_retention_archived_by_agent_id_agents_id_fk": { + "name": "decision_retention_archived_by_agent_id_agents_id_fk", + "tableFrom": "decision_retention", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_retention_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_retention_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_retention", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_retention_archive_actor_check": { + "name": "decision_retention_archive_actor_check", + "value": "(\n (\"decision_retention\".\"archived_at\" IS NULL AND \"decision_retention\".\"archived_by_type\" IS NULL AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'system' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'agent' AND \"decision_retention\".\"archived_by_agent_id\" IS NOT NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'user' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage": { + "name": "decision_triage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decide_by": { + "name": "decide_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decide_by_date": { + "name": "decide_by_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "set_by_type": { + "name": "set_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "set_by_agent_id": { + "name": "set_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_user_id": { + "name": "set_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "set_by_run_id": { + "name": "set_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_agent_api_key_id": { + "name": "set_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_triage_company_source_uq": { + "name": "decision_triage_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_company_decide_by_idx": { + "name": "decision_triage_company_decide_by_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decide_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_company_id_companies_id_fk": { + "name": "decision_triage_company_id_companies_id_fk", + "tableFrom": "decision_triage", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_id_agents_id_fk": { + "name": "decision_triage_set_by_agent_id_agents_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agents", + "columnsFrom": [ + "set_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_set_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "set_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "set_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_actor_check": { + "name": "decision_triage_actor_check", + "value": "(\n (\"decision_triage\".\"set_by_type\" = 'agent' AND \"decision_triage\".\"set_by_agent_id\" IS NOT NULL AND \"decision_triage\".\"set_by_user_id\" IS NULL)\n OR (\"decision_triage\".\"set_by_type\" = 'user' AND \"decision_triage\".\"set_by_agent_id\" IS NULL AND \"decision_triage\".\"set_by_user_id\" IS NOT NULL)\n )" + }, + "decision_triage_decide_by_check": { + "name": "decision_triage_decide_by_check", + "value": "(\n (\"decision_triage\".\"decide_by\" IS NULL AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" IN ('today', 'this_week', 'whenever') AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" = 'date' AND \"decision_triage\".\"decide_by_date\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage_events": { + "name": "decision_triage_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_run_id": { + "name": "actor_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_api_key_id": { + "name": "agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_triage_events_company_source_created_idx": { + "name": "decision_triage_events_company_source_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_events_queue_created_idx": { + "name": "decision_triage_events_queue_created_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_events_company_id_companies_id_fk": { + "name": "decision_triage_events_company_id_companies_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_events_queue_id_decision_queues_id_fk": { + "name": "decision_triage_events_queue_id_decision_queues_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_agent_id_agents_id_fk": { + "name": "decision_triage_events_actor_agent_id_agents_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_events_actor_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "actor_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_events_actor_check": { + "name": "decision_triage_events_actor_check", + "value": "(\n (\"decision_triage_events\".\"actor_type\" = 'agent' AND \"decision_triage_events\".\"actor_agent_id\" IS NOT NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'user' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NOT NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'system' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_training_examples": { + "name": "decision_training_examples", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cutoff_at": { + "name": "cutoff_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "notes_history": { + "name": "notes_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "decision_outcome": { + "name": "decision_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scrub_deleted_comments_v1'" + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_training_examples_company_created_at_idx": { + "name": "decision_training_examples_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_issue_idx": { + "name": "decision_training_examples_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_source_author_uq": { + "name": "decision_training_examples_source_author_uq", + "columns": [ + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_training_examples_company_id_companies_id_fk": { + "name": "decision_training_examples_company_id_companies_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_training_examples_issue_id_issues_id_fk": { + "name": "decision_training_examples_issue_id_issues_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_bundles": { + "name": "decision_bundles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_bundles_company_created_at_idx": { + "name": "decision_bundles_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_bundles_company_id_companies_id_fk": { + "name": "decision_bundles_company_id_companies_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_agent_id_agents_id_fk": { + "name": "decision_bundles_origin_agent_id_agents_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_issue_id_issues_id_fk": { + "name": "decision_bundles_origin_issue_id_issues_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_run_id_heartbeat_runs_id_fk": { + "name": "decision_bundles_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_effect_executions": { + "name": "decision_effect_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effect_index": { + "name": "effect_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_type": { + "name": "effect_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claimed'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activity_log_id": { + "name": "activity_log_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_effect_executions_decision_effect_uq": { + "name": "decision_effect_executions_decision_effect_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "effect_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_effect_executions_target_issue_idx": { + "name": "decision_effect_executions_target_issue_idx", + "columns": [ + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_effect_executions_decision_id_decisions_id_fk": { + "name": "decision_effect_executions_decision_id_decisions_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_effect_executions_target_issue_id_issues_id_fk": { + "name": "decision_effect_executions_target_issue_id_issues_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_effect_executions_activity_log_id_activity_log_id_fk": { + "name": "decision_effect_executions_activity_log_id_activity_log_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "activity_log", + "columnsFrom": [ + "activity_log_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_target_issues": { + "name": "decision_target_issues", + "schema": "", + "columns": { + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "decision_target_issues_decision_idx": { + "name": "decision_target_issues_decision_idx", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_target_issues_issue_idx": { + "name": "decision_target_issues_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_target_issues_decision_id_decisions_id_fk": { + "name": "decision_target_issues_decision_id_decisions_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_issue_id_issues_id_fk": { + "name": "decision_target_issues_issue_id_issues_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_company_id_companies_id_fk": { + "name": "decision_target_issues_company_id_companies_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "decision_target_issues_decision_id_issue_id_pk": { + "name": "decision_target_issues_decision_id_issue_id_pk", + "columns": [ + "decision_id", + "issue_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decisions": { + "name": "decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_id": { + "name": "bundle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "rule_key": { + "name": "rule_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "inputs": { + "name": "inputs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "execution_status": { + "name": "execution_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chosen_option_id": { + "name": "chosen_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_values": { + "name": "input_values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signed_spec": { + "name": "signed_spec", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_snapshots": { + "name": "target_snapshots", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decisions_company_status_expires_at_idx": { + "name": "decisions_company_status_expires_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_bundle_idx": { + "name": "decisions_bundle_idx", + "columns": [ + { + "expression": "bundle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_origin_issue_idx": { + "name": "decisions_origin_issue_idx", + "columns": [ + { + "expression": "origin_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_company_idempotency_uq": { + "name": "decisions_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"decisions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decisions_company_id_companies_id_fk": { + "name": "decisions_company_id_companies_id_fk", + "tableFrom": "decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_bundle_id_decision_bundles_id_fk": { + "name": "decisions_bundle_id_decision_bundles_id_fk", + "tableFrom": "decisions", + "tableTo": "decision_bundles", + "columnsFrom": [ + "bundle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "decisions_origin_agent_id_agents_id_fk": { + "name": "decisions_origin_agent_id_agents_id_fk", + "tableFrom": "decisions", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_issue_id_issues_id_fk": { + "name": "decisions_origin_issue_id_issues_id_fk", + "tableFrom": "decisions", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_run_id_heartbeat_runs_id_fk": { + "name": "decisions_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_anchor_snapshots": { + "name": "document_annotation_anchor_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_revision_id": { + "name": "from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_revision_number": { + "name": "from_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_revision_id": { + "name": "to_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_revision_number": { + "name": "to_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_anchor": { + "name": "previous_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "next_anchor": { + "name": "next_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_anchor_snapshots_company_thread_created_at_idx": { + "name": "document_annotation_anchor_snapshots_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_anchor_snapshots_company_document_revision_idx": { + "name": "document_annotation_anchor_snapshots_company_document_revision_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_anchor_snapshots_company_id_companies_id_fk": { + "name": "document_annotation_anchor_snapshots_company_id_companies_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_document_id_documents_id_fk": { + "name": "document_annotation_anchor_snapshots_document_id_documents_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "to_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_comments": { + "name": "document_annotation_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_comments_company_thread_created_at_idx": { + "name": "document_annotation_comments_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_issue_created_at_idx": { + "name": "document_annotation_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_routine_created_at_idx": { + "name": "document_annotation_comments_company_routine_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_case_created_at_idx": { + "name": "document_annotation_comments_company_case_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_document_created_at_idx": { + "name": "document_annotation_comments_company_document_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_issue_comment_idx": { + "name": "document_annotation_comments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_body_search_idx": { + "name": "document_annotation_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_comments_company_id_companies_id_fk": { + "name": "document_annotation_comments_company_id_companies_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_comments_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_comments_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_id_issues_id_fk": { + "name": "document_annotation_comments_issue_id_issues_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_routine_id_routines_id_fk": { + "name": "document_annotation_comments_routine_id_routines_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_case_id_cases_id_fk": { + "name": "document_annotation_comments_case_id_cases_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_document_id_documents_id_fk": { + "name": "document_annotation_comments_document_id_documents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_author_agent_id_agents_id_fk": { + "name": "document_annotation_comments_author_agent_id_agents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_comment_id_issue_comments_id_fk": { + "name": "document_annotation_comments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_comments_exactly_one_owner_chk": { + "name": "document_annotation_comments_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_comments\".\"issue_id\", \"document_annotation_comments\".\"routine_id\", \"document_annotation_comments\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_annotation_threads": { + "name": "document_annotation_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "original_revision_id": { + "name": "original_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_revision_number": { + "name": "original_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_revision_id": { + "name": "current_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "current_revision_number": { + "name": "current_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "selected_text": { + "name": "selected_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix_text": { + "name": "prefix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "suffix_text": { + "name": "suffix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "normalized_start": { + "name": "normalized_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "normalized_end": { + "name": "normalized_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_start": { + "name": "markdown_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_end": { + "name": "markdown_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "anchor_selector": { + "name": "anchor_selector", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_threads_company_document_status_idx": { + "name": "document_annotation_threads_company_document_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_issue_status_idx": { + "name": "document_annotation_threads_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_routine_status_idx": { + "name": "document_annotation_threads_company_routine_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_case_status_idx": { + "name": "document_annotation_threads_company_case_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_current_revision_open_idx": { + "name": "document_annotation_threads_company_current_revision_open_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_anchor_state_idx": { + "name": "document_annotation_threads_company_anchor_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anchor_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_threads_company_id_companies_id_fk": { + "name": "document_annotation_threads_company_id_companies_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_threads_issue_id_issues_id_fk": { + "name": "document_annotation_threads_issue_id_issues_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_routine_id_routines_id_fk": { + "name": "document_annotation_threads_routine_id_routines_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_case_id_cases_id_fk": { + "name": "document_annotation_threads_case_id_cases_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_document_id_documents_id_fk": { + "name": "document_annotation_threads_document_id_documents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_original_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_original_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "original_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_current_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_current_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "current_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_created_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_created_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_resolved_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_resolved_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_threads_exactly_one_owner_chk": { + "name": "document_annotation_threads_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_threads\".\"issue_id\", \"document_annotation_threads\".\"routine_id\", \"document_annotation_threads\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_memberships": { + "name": "document_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_memberships_company_user_starred_idx": { + "name": "document_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_memberships_company_user_document_uq": { + "name": "document_memberships_company_user_document_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_memberships_company_id_companies_id_fk": { + "name": "document_memberships_company_id_companies_id_fk", + "tableFrom": "document_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_memberships_document_id_documents_id_fk": { + "name": "document_memberships_document_id_documents_id_fk", + "tableFrom": "document_memberships", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "locked_by_agent_id": { + "name": "locked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "locked_by_user_id": { + "name": "locked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_title_search_idx": { + "name": "documents_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "documents_latest_body_search_idx": { + "name": "documents_latest_body_search_idx", + "columns": [ + { + "expression": "latest_body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_locked_by_agent_id_agents_id_fk": { + "name": "documents_locked_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "locked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_custom_image_setup_sessions": { + "name": "environment_custom_image_setup_sessions", + "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 + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "promoted_template_id": { + "name": "promoted_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment_lease_id": { + "name": "environment_lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_by_agent_id": { + "name": "started_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_template_ref": { + "name": "base_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_summary": { + "name": "connection_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_secret_ref": { + "name": "connection_secret_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_setup_sessions_environment_status_idx": { + "name": "environment_custom_image_setup_sessions_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_environment_active_uq": { + "name": "environment_custom_image_setup_sessions_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_setup_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'capturing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_template_idx": { + "name": "environment_custom_image_setup_sessions_template_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_promoted_template_idx": { + "name": "environment_custom_image_setup_sessions_promoted_template_idx", + "columns": [ + { + "expression": "promoted_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_expires_idx": { + "name": "environment_custom_image_setup_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_provider_lease_idx": { + "name": "environment_custom_image_setup_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_setup_sessions_environment_id_environments_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "promoted_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_leases", + "columnsFrom": [ + "environment_lease_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "agents", + "columnsFrom": [ + "started_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_custom_image_templates": { + "name": "environment_custom_image_templates", + "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 + }, + "template_kind": { + "name": "template_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "template_ref": { + "name": "template_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_template_ref": { + "name": "source_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_environment_config_fingerprint": { + "name": "source_environment_config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_by_template_id": { + "name": "superseded_by_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_templates_environment_status_idx": { + "name": "environment_custom_image_templates_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_provider_status_idx": { + "name": "environment_custom_image_templates_environment_provider_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_active_uq": { + "name": "environment_custom_image_templates_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_templates\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_superseded_by_idx": { + "name": "environment_custom_image_templates_superseded_by_idx", + "columns": [ + { + "expression": "superseded_by_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_last_used_idx": { + "name": "environment_custom_image_templates_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_templates_environment_id_environments_id_fk": { + "name": "environment_custom_image_templates_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_templates_created_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "superseded_by_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_leases": { + "name": "environment_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "lease_policy": { + "name": "lease_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ephemeral'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acquired_at": { + "name": "acquired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_status": { + "name": "cleanup_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_leases_company_environment_status_idx": { + "name": "environment_leases_company_environment_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_execution_workspace_idx": { + "name": "environment_leases_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_issue_idx": { + "name": "environment_leases_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_heartbeat_run_idx": { + "name": "environment_leases_heartbeat_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_last_used_idx": { + "name": "environment_leases_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_provider_lease_idx": { + "name": "environment_leases_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_leases_company_id_companies_id_fk": { + "name": "environment_leases_company_id_companies_id_fk", + "tableFrom": "environment_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_leases_environment_id_environments_id_fk": { + "name": "environment_leases_environment_id_environments_id_fk", + "tableFrom": "environment_leases", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "environment_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "environment_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_issue_id_issues_id_fk": { + "name": "environment_leases_issue_id_issues_id_fk", + "tableFrom": "environment_leases", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "environment_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver": { + "name": "driver", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "env_vars": { + "name": "env_vars", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environments_status_idx": { + "name": "environments_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_local_driver_idx": { + "name": "environments_local_driver_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'local'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_managed_sandbox_idx": { + "name": "environments_managed_sandbox_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'sandbox' AND (\"environments\".\"metadata\" ->> 'managedByPaperclip')::boolean = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_name_idx": { + "name": "environments_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_runtime_leases": { + "name": "execution_workspace_runtime_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_key": { + "name": "owner_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_issue_id": { + "name": "owner_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_action": { + "name": "last_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "renewed_at": { + "name": "renewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_runtime_leases_company_workspace_idx": { + "name": "execution_workspace_runtime_leases_company_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_company_owner_idx": { + "name": "execution_workspace_runtime_leases_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_expires_at_idx": { + "name": "execution_workspace_runtime_leases_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_runtime_leases_company_id_companies_id_fk": { + "name": "execution_workspace_runtime_leases_company_id_companies_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk": { + "name": "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "issues", + "columnsFrom": [ + "owner_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk": { + "name": "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk": { + "name": "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "execution_workspace_runtime_leases_execution_workspace_id_unique": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_unique", + "nullsNotDistinct": false, + "columns": [ + "execution_workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_object_mentions": { + "name": "external_object_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "property_key": { + "name": "property_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text_redacted": { + "name": "matched_text_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sanitized_display_url": { + "name": "sanitized_display_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity": { + "name": "canonical_identity", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detector_key": { + "name": "detector_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "created_by_plugin_id": { + "name": "created_by_plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_object_mentions_company_source_issue_idx": { + "name": "external_object_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_object_idx": { + "name": "external_object_mentions_company_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_provider_idx": { + "name": "external_object_mentions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_record_uq": { + "name": "external_object_mentions_company_source_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is not null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_null_record_uq": { + "name": "external_object_mentions_company_source_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_object_mentions_company_id_companies_id_fk": { + "name": "external_object_mentions_company_id_companies_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_source_issue_id_issues_id_fk": { + "name": "external_object_mentions_source_issue_id_issues_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_object_id_external_objects_id_fk": { + "name": "external_object_mentions_object_id_external_objects_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "external_objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "external_object_mentions_created_by_plugin_id_plugins_id_fk": { + "name": "external_object_mentions_created_by_plugin_id_plugins_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "plugins", + "columnsFrom": [ + "created_by_plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_objects": { + "name": "external_objects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sanitized_canonical_url": { + "name": "sanitized_canonical_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_key": { + "name": "display_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_key": { + "name": "icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_title": { + "name": "display_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_key": { + "name": "status_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_label": { + "name": "status_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_icon_key": { + "name": "status_icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_category": { + "name": "status_category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "status_tone": { + "name": "status_tone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "liveness": { + "name": "liveness", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "is_terminal": { + "name": "is_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "remote_version": { + "name": "remote_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_changed_at": { + "name": "last_changed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_refresh_at": { + "name": "next_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_started_at": { + "name": "refresh_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_objects_company_provider_object_idx": { + "name": "external_objects_company_provider_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_provider_status_idx": { + "name": "external_objects_company_provider_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_refresh_idx": { + "name": "external_objects_company_refresh_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_refresh_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_external_id_uq": { + "name": "external_objects_company_external_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_identity_uq": { + "name": "external_objects_company_identity_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_objects_company_id_companies_id_fk": { + "name": "external_objects_company_id_companies_id_fk", + "tableFrom": "external_objects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_objects_plugin_id_plugins_id_fk": { + "name": "external_objects_plugin_id_plugins_id_fk", + "tableFrom": "external_objects", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folders": { + "name": "folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "system_key": { + "name": "system_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "folders_company_kind_position_idx": { + "name": "folders_company_kind_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_root_slug_uq": { + "name": "folders_company_kind_root_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_slug_uq": { + "name": "folders_company_kind_parent_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_system_key_uq": { + "name": "folders_company_kind_system_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "system_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"system_key\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_position_idx": { + "name": "folders_company_kind_parent_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folders_company_id_companies_id_fk": { + "name": "folders_company_id_companies_id_fk", + "tableFrom": "folders", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folders_parent_id_folders_id_fk": { + "name": "folders_parent_id_folders_id_fk", + "tableFrom": "folders", + "tableTo": "folders", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_instance_id": { + "name": "source_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_seq": { + "name": "source_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_payload_sha256": { + "name": "source_payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protocol_schema_version": { + "name": "protocol_schema_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_idx": { + "name": "heartbeat_run_events_run_seq_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_event_uq": { + "name": "heartbeat_run_events_run_source_event_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_event_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_seq_uq": { + "name": "heartbeat_run_events_run_source_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_instance_id\" is not null and \"heartbeat_run_events\".\"source_seq\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_watchdog_decisions": { + "name": "heartbeat_run_watchdog_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evaluation_issue_id": { + "name": "evaluation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_watchdog_decisions_company_run_created_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "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": {} + }, + "heartbeat_run_watchdog_decisions_company_run_snooze_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_snooze_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snoozed_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_watchdog_decisions_company_id_companies_id_fk": { + "name": "heartbeat_run_watchdog_decisions_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk": { + "name": "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "issues", + "columnsFrom": [ + "evaluation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runtime_mode": { + "name": "runtime_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'legacy'" + }, + "runtime_mode_resolver_version": { + "name": "runtime_mode_resolver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_reason": { + "name": "runtime_mode_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_resolved_at": { + "name": "runtime_mode_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "runner_profile_json": { + "name": "runner_profile_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runner_instance_id": { + "name": "runner_instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_session_id": { + "name": "native_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_issue_id": { + "name": "native_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "driver_kind": { + "name": "driver_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver_version": { + "name": "driver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completion_contract_sha256": { + "name": "completion_contract_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_event_seq": { + "name": "next_event_seq", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "native_phase": { + "name": "native_phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "native_phase_updated_at": { + "name": "native_phase_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_group_id": { + "name": "process_group_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_at": { + "name": "last_output_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_seq": { + "name": "last_output_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_output_stream": { + "name": "last_output_stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_output_bytes": { + "name": "last_output_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_at": { + "name": "scheduled_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scheduled_retry_attempt": { + "name": "scheduled_retry_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_reason": { + "name": "scheduled_retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_comment_status": { + "name": "issue_comment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_applicable'" + }, + "issue_comment_satisfied_by_comment_id": { + "name": "issue_comment_satisfied_by_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_retry_queued_at": { + "name": "issue_comment_retry_queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "liveness_state": { + "name": "liveness_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "liveness_reason": { + "name": "liveness_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "continuation_attempt": { + "name": "continuation_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_useful_action_at": { + "name": "last_useful_action_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_responsible_user_idx": { + "name": "heartbeat_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_liveness_idx": { + "name": "heartbeat_runs_company_liveness_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "liveness_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_last_output_idx": { + "name": "heartbeat_runs_company_status_last_output_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_output_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_process_started_idx": { + "name": "heartbeat_runs_company_status_process_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "process_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_created_at_desc_idx": { + "name": "heartbeat_runs_company_created_at_desc_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_issue_created_idx": { + "name": "heartbeat_runs_company_ctx_issue_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_task_created_idx": { + "name": "heartbeat_runs_company_ctx_task_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_taskkey_created_idx": { + "name": "heartbeat_runs_company_ctx_taskkey_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskKey')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "heartbeat_runs_company_native_issue_id_uq": { + "name": "heartbeat_runs_company_native_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id" + ] + }, + "heartbeat_runs_company_native_issue_contract_id_uq": { + "name": "heartbeat_runs_company_native_issue_contract_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inbox_dismissals": { + "name": "inbox_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_key": { + "name": "item_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'dismiss'" + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_dismissals_company_user_idx": { + "name": "inbox_dismissals_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_item_idx": { + "name": "inbox_dismissals_company_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_user_item_idx": { + "name": "inbox_dismissals_company_user_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inbox_dismissals_company_id_companies_id_fk": { + "name": "inbox_dismissals_company_id_companies_id_fk", + "tableFrom": "inbox_dismissals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_delegations": { + "name": "connection_grant_delegations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_delegations_company_agent_idx": { + "name": "connection_grant_delegations_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_delegations_grant_agent_uq": { + "name": "connection_grant_delegations_grant_agent_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_delegations_company_id_companies_id_fk": { + "name": "connection_grant_delegations_company_id_companies_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_agent_id_agents_id_fk": { + "name": "connection_grant_delegations_agent_id_agents_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_company_grant_fk": { + "name": "connection_grant_delegations_company_grant_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_members": { + "name": "connection_grant_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_members_company_subject_idx": { + "name": "connection_grant_members_company_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_members_grant_subject_uq": { + "name": "connection_grant_members_grant_subject_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_members_company_id_companies_id_fk": { + "name": "connection_grant_members_company_id_companies_id_fk", + "tableFrom": "connection_grant_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_members_company_grant_fk": { + "name": "connection_grant_members_company_grant_fk", + "tableFrom": "connection_grant_members", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "connection_grant_members_subject_type_check": { + "name": "connection_grant_members_subject_type_check", + "value": "\"connection_grant_members\".\"subject_type\" in ('user')" + } + }, + "isRLSEnabled": false + }, + "public.connection_grants": { + "name": "connection_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_tenant": { + "name": "provider_tenant", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_by_agent_id": { + "name": "revoked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revoked_by_user_id": { + "name": "revoked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grants_company_connection_idx": { + "name": "connection_grants_company_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_user_idx": { + "name": "connection_grants_subject_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_user_uq": { + "name": "connection_grants_user_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_default_uq": { + "name": "connection_grants_default_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"connection_grants\".\"is_default\" = true and \"connection_grants\".\"kind\" = 'organization'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grants_company_id_companies_id_fk": { + "name": "connection_grants_company_id_companies_id_fk", + "tableFrom": "connection_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_created_by_agent_id_agents_id_fk": { + "name": "connection_grants_created_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_revoked_by_agent_id_agents_id_fk": { + "name": "connection_grants_revoked_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "revoked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_company_connection_fk": { + "name": "connection_grants_company_connection_fk", + "tableFrom": "connection_grants", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connection_grants_company_id_uq": { + "name": "connection_grants_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "connection_grants_kind_check": { + "name": "connection_grants_kind_check", + "value": "\"connection_grants\".\"kind\" in ('organization', 'user')" + }, + "connection_grants_status_check": { + "name": "connection_grants_status_check", + "value": "\"connection_grants\".\"status\" in ('active', 'revoked', 'expired', 'needs_reauthorization')" + }, + "connection_grants_subject_check": { + "name": "connection_grants_subject_check", + "value": "(\"connection_grants\".\"kind\" = 'user' and \"connection_grants\".\"subject_user_id\" is not null) or (\"connection_grants\".\"kind\" = 'organization' and \"connection_grants\".\"subject_user_id\" is null)" + }, + "connection_grants_default_check": { + "name": "connection_grants_default_check", + "value": "\"connection_grants\".\"is_default\" = false or \"connection_grants\".\"kind\" = 'organization'" + } + }, + "isRLSEnabled": false + }, + "public.connection_token_issuances": { + "name": "connection_token_issuances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_scope": { + "name": "requested_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "issued_scope": { + "name": "issued_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "ttl_seconds": { + "name": "ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_token_issuances_company_created_idx": { + "name": "connection_token_issuances_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_connection_created_idx": { + "name": "connection_token_issuances_connection_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_agent_connection_idx": { + "name": "connection_token_issuances_agent_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_run_idx": { + "name": "connection_token_issuances_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_token_issuances_company_id_companies_id_fk": { + "name": "connection_token_issuances_company_id_companies_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_application_id_tool_applications_id_fk": { + "name": "connection_token_issuances_application_id_tool_applications_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_connection_id_tool_connections_id_fk": { + "name": "connection_token_issuances_connection_id_tool_connections_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_agent_id_agents_id_fk": { + "name": "connection_token_issuances_agent_id_agents_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_run_id_heartbeat_runs_id_fk": { + "name": "connection_token_issuances_run_id_heartbeat_runs_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_issue_id_issues_id_fk": { + "name": "connection_token_issuances_issue_id_issues_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_project_id_projects_id_fk": { + "name": "connection_token_issuances_project_id_projects_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "instance_settings_default_environment_id_environments_id_fk": { + "name": "instance_settings_default_environment_id_environments_id_fk", + "tableFrom": "instance_settings", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "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 + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_behalf_of_user_id": { + "name": "on_behalf_of_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_agent_id": { + "name": "derived_author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_created_by_run_id": { + "name": "derived_created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_source": { + "name": "derived_author_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "presentation": { + "name": "presentation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by_type": { + "name": "deleted_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_agent_id": { + "name": "deleted_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deleted_by_user_id": { + "name": "deleted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_run_id": { + "name": "deleted_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_on_behalf_of_user_id_user_id_fk": { + "name": "issue_comments_on_behalf_of_user_id_user_id_fk", + "tableFrom": "issue_comments", + "tableTo": "user", + "columnsFrom": [ + "on_behalf_of_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_author_agent_id_agents_id_fk": { + "name": "issue_comments_derived_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "derived_author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "derived_created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_agent_id_agents_id_fk": { + "name": "issue_comments_deleted_by_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "deleted_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "deleted_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_create_idempotency_keys": { + "name": "issue_create_idempotency_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_create_idempotency_keys_company_key_uq": { + "name": "issue_create_idempotency_keys_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_issue_idx": { + "name": "issue_create_idempotency_keys_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_company_created_at_idx": { + "name": "issue_create_idempotency_keys_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_create_idempotency_keys_company_id_companies_id_fk": { + "name": "issue_create_idempotency_keys_company_id_companies_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_create_idempotency_keys_issue_id_issues_id_fk": { + "name": "issue_create_idempotency_keys_issue_id_issues_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_execution_decisions": { + "name": "issue_execution_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_type": { + "name": "stage_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_execution_decisions_company_issue_idx": { + "name": "issue_execution_decisions_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_execution_decisions_stage_idx": { + "name": "issue_execution_decisions_stage_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_execution_decisions_company_id_companies_id_fk": { + "name": "issue_execution_decisions_company_id_companies_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_issue_id_issues_id_fk": { + "name": "issue_execution_decisions_issue_id_issues_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_execution_decisions_actor_agent_id_agents_id_fk": { + "name": "issue_execution_decisions_actor_agent_id_agents_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_by_actor_type": { + "name": "archived_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_agent_id_agents_id_fk": { + "name": "issue_inbox_archives_archived_by_agent_id_agents_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_inbox_archives_archived_by_actor_type_check": { + "name": "issue_inbox_archives_archived_by_actor_type_check", + "value": "\"issue_inbox_archives\".\"archived_by_actor_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_plan_decompositions": { + "name": "issue_plan_decompositions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_plan_revision_id": { + "name": "accepted_plan_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_interaction_id": { + "name": "accepted_interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_flight'" + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_child_count": { + "name": "requested_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_children": { + "name": "requested_children", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "child_issue_ids": { + "name": "child_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_plan_decompositions_company_source_status_idx": { + "name": "issue_plan_decompositions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_active_owner_idx": { + "name": "issue_plan_decompositions_active_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issue_plan_decompositions\".\"status\" = 'in_flight'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_source_revision_uq": { + "name": "issue_plan_decompositions_source_revision_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_plan_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_plan_decompositions_company_id_companies_id_fk": { + "name": "issue_plan_decompositions_company_id_companies_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_plan_decompositions_source_issue_id_issues_id_fk": { + "name": "issue_plan_decompositions_source_issue_id_issues_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk": { + "name": "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "document_revisions", + "columnsFrom": [ + "accepted_plan_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "accepted_interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_agent_id_agents_id_fk": { + "name": "issue_plan_decompositions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk": { + "name": "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_question_response_deliveries": { + "name": "issue_question_response_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_run_id": { + "name": "target_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_turn_id": { + "name": "target_turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_sha256": { + "name": "payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "delivery_mode": { + "name": "delivery_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_count": { + "name": "error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_question_response_deliveries_interaction_uq": { + "name": "issue_question_response_deliveries_interaction_uq", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_correlation_uq": { + "name": "issue_question_response_deliveries_correlation_uq", + "columns": [ + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_pending_idx": { + "name": "issue_question_response_deliveries_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_company_issue_idx": { + "name": "issue_question_response_deliveries_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_question_response_deliveries_company_id_companies_id_fk": { + "name": "issue_question_response_deliveries_company_id_companies_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_issue_id_issues_id_fk": { + "name": "issue_question_response_deliveries_issue_id_issues_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "target_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_question_response_deliveries_status_check": { + "name": "issue_question_response_deliveries_status_check", + "value": "\"issue_question_response_deliveries\".\"status\" IN ('pending', 'delivering', 'delivered', 'fallback_queued', 'failed')" + }, + "issue_question_response_deliveries_mode_check": { + "name": "issue_question_response_deliveries_mode_check", + "value": "\"issue_question_response_deliveries\".\"delivery_mode\" IS NULL OR \"issue_question_response_deliveries\".\"delivery_mode\" IN ('steered', 'coalesced', 'wake_fallback')" + } + }, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_recovery_actions": { + "name": "issue_recovery_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recovery_issue_id": { + "name": "recovery_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_owner_agent_id": { + "name": "previous_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "return_owner_agent_id": { + "name": "return_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wake_policy": { + "name": "wake_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_policy": { + "name": "monitor_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_note": { + "name": "resolution_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_recovery_actions_company_source_status_idx": { + "name": "issue_recovery_actions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_owner_status_idx": { + "name": "issue_recovery_actions_company_owner_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_recovery_issue_idx": { + "name": "issue_recovery_actions_company_recovery_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recovery_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_source_uq": { + "name": "issue_recovery_actions_active_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_fingerprint_uq": { + "name": "issue_recovery_actions_active_fingerprint_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cause", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_recovery_actions_company_id_companies_id_fk": { + "name": "issue_recovery_actions_company_id_companies_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_recovery_actions_source_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_source_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_recovery_actions_recovery_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_recovery_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "recovery_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_previous_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_previous_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "previous_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_return_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_return_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "return_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_reference_mentions": { + "name": "issue_reference_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text": { + "name": "matched_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_reference_mentions_company_source_issue_idx": { + "name": "issue_reference_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_target_issue_idx": { + "name": "issue_reference_mentions_company_target_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_issue_pair_idx": { + "name": "issue_reference_mentions_company_issue_pair_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_record_uq": { + "name": "issue_reference_mentions_company_source_mention_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_null_record_uq": { + "name": "issue_reference_mentions_company_source_mention_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_reference_mentions_company_id_companies_id_fk": { + "name": "issue_reference_mentions_company_id_companies_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_reference_mentions_source_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_source_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_reference_mentions_target_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_target_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_thread_interactions": { + "name": "issue_thread_interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wake_assignee'" + }, + "requested_resolver_policy": { + "name": "requested_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "effective_resolver_policy": { + "name": "effective_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "resolver_policy_provenance": { + "name": "resolver_policy_provenance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherited'" + }, + "effective_resolver_policy_source": { + "name": "effective_resolver_policy_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_agent_id": { + "name": "addressee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_user_id": { + "name": "addressee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_run_id": { + "name": "resolved_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_thread_interactions_issue_idx": { + "name": "issue_thread_interactions_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_created_at_idx": { + "name": "issue_thread_interactions_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_status_idx": { + "name": "issue_thread_interactions_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_idempotency_uq": { + "name": "issue_thread_interactions_company_issue_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_thread_interactions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_source_comment_idx": { + "name": "issue_thread_interactions_source_comment_idx", + "columns": [ + { + "expression": "source_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_agent_idx": { + "name": "issue_thread_interactions_addressee_agent_idx", + "columns": [ + { + "expression": "addressee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_user_idx": { + "name": "issue_thread_interactions_addressee_user_idx", + "columns": [ + { + "expression": "addressee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_thread_interactions_company_id_companies_id_fk": { + "name": "issue_thread_interactions_company_id_companies_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_issue_id_issues_id_fk": { + "name": "issue_thread_interactions_issue_id_issues_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_comment_id_issue_comments_id_fk": { + "name": "issue_thread_interactions_source_comment_id_issue_comments_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issue_comments", + "columnsFrom": [ + "source_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_created_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_addressee_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_addressee_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "addressee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_resolved_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "resolved_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_hold_members": { + "name": "issue_tree_hold_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hold_id": { + "name": "hold_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "issue_identifier": { + "name": "issue_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_status": { + "name": "issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_run_id": { + "name": "active_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "active_run_status": { + "name": "active_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skipped": { + "name": "skipped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_hold_members_hold_issue_uq": { + "name": "issue_tree_hold_members_hold_issue_uq", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_company_issue_idx": { + "name": "issue_tree_hold_members_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_hold_depth_idx": { + "name": "issue_tree_hold_members_hold_depth_idx", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depth", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_hold_members_company_id_companies_id_fk": { + "name": "issue_tree_hold_members_company_id_companies_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk": { + "name": "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issue_tree_holds", + "columnsFrom": [ + "hold_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_parent_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_parent_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_assignee_agent_id_agents_id_fk": { + "name": "issue_tree_hold_members_assignee_agent_id_agents_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "active_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_holds": { + "name": "issue_tree_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_issue_id": { + "name": "root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_policy": { + "name": "release_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_by_actor_type": { + "name": "released_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_agent_id": { + "name": "released_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_by_user_id": { + "name": "released_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_run_id": { + "name": "released_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "release_reason": { + "name": "release_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_metadata": { + "name": "release_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_holds_company_root_status_idx": { + "name": "issue_tree_holds_company_root_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_holds_company_status_mode_idx": { + "name": "issue_tree_holds_company_status_mode_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_holds_company_id_companies_id_fk": { + "name": "issue_tree_holds_company_id_companies_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_holds_root_issue_id_issues_id_fk": { + "name": "issue_tree_holds_root_issue_id_issues_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "issues", + "columnsFrom": [ + "root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_released_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "released_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "released_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_watchdogs": { + "name": "issue_watchdogs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "watchdog_agent_id": { + "name": "watchdog_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "watchdog_issue_id": { + "name": "watchdog_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_observed_fingerprint": { + "name": "last_observed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_fingerprint": { + "name": "last_reviewed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_observed_stop_snapshot": { + "name": "last_observed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_stop_snapshot": { + "name": "last_reviewed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trigger_count": { + "name": "trigger_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_run_id": { + "name": "updated_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_watchdogs_company_issue_uq": { + "name": "issue_watchdogs_company_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_status_idx": { + "name": "issue_watchdogs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_agent_idx": { + "name": "issue_watchdogs_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_watchdog_issue_uq": { + "name": "issue_watchdogs_company_watchdog_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_watchdogs\".\"watchdog_issue_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_watchdogs_company_id_companies_id_fk": { + "name": "issue_watchdogs_company_id_companies_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_issue_id_issues_id_fk": { + "name": "issue_watchdogs_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_agent_id_agents_id_fk": { + "name": "issue_watchdogs_watchdog_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "watchdog_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_issue_id_issues_id_fk": { + "name": "issue_watchdogs_watchdog_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "watchdog_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_updated_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "updated_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "status_version": { + "name": "status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_status_decision_id": { + "name": "last_status_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "work_mode": { + "name": "work_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "harness_kind": { + "name": "harness_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "review_policy": { + "name": "review_policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_fingerprint": { + "name": "origin_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_policy": { + "name": "execution_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_state": { + "name": "execution_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_next_check_at": { + "name": "monitor_next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_wake_requested_at": { + "name": "monitor_wake_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_last_triggered_at": { + "name": "monitor_last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_attempt_count": { + "name": "monitor_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "monitor_notes": { + "name": "monitor_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "monitor_scheduled_by": { + "name": "monitor_scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "unblock_descriptor": { + "name": "unblock_descriptor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "blocked_transition_at": { + "name": "blocked_transition_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_owner_notified_at": { + "name": "blocked_owner_notified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_harness_kind_idx": { + "name": "issues_company_harness_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_responsible_user_idx": { + "name": "issues_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_monitor_due_idx": { + "name": "issues_company_monitor_due_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "monitor_next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_updated_idx": { + "name": "issues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_created_idx": { + "name": "issues_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_normalized_title_created_idx": { + "name": "issues_open_normalized_title_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(regexp_replace(btrim(\"title\"), '\\s+', ' ', 'g'))", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issues\".\"hidden_at\" is null and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_priority_idx": { + "name": "issues_company_priority_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_incident_uq": { + "name": "issues_active_liveness_recovery_incident_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_leaf_uq": { + "name": "issues_active_liveness_recovery_leaf_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_fingerprint\" <> 'default'\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stale_run_evaluation_uq": { + "name": "issues_active_stale_run_evaluation_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stale_active_run_evaluation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_task_watchdog_uq": { + "name": "issues_active_task_watchdog_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'task_watchdog'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_productivity_review_uq": { + "name": "issues_active_productivity_review_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'issue_productivity_review'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stranded_issue_recovery_uq": { + "name": "issues_active_stranded_issue_recovery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stranded_issue_recovery'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_onboarding_first_task_uq": { + "name": "issues_onboarding_first_task_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'onboarding_first_task'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issues_company_id_uq": { + "name": "issues_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_user_uq": { + "name": "join_requests_pending_human_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requesting_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"requesting_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_email_uq": { + "name": "join_requests_pending_human_email_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"request_email_snapshot\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"request_email_snapshot\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.native_run_finalizations": { + "name": "native_run_finalizations", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_owner": { + "name": "lease_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_detail": { + "name": "failure_detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "native_run_finalizations_company_id_companies_id_fk": { + "name": "native_run_finalizations_company_id_companies_id_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_issue_company_fk": { + "name": "native_run_finalizations_issue_company_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_run_owner_fk": { + "name": "native_run_finalizations_run_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_result_owner_fk": { + "name": "native_run_finalizations_result_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_assessment_owner_fk": { + "name": "native_run_finalizations_assessment_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_decision_owner_fk": { + "name": "native_run_finalizations_decision_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "native_run_finalizations_assessment_requires_result_check": { + "name": "native_run_finalizations_assessment_requires_result_check", + "value": "\"native_run_finalizations\".\"assessment_id\" is null or \"native_run_finalizations\".\"result_id\" is not null" + }, + "native_run_finalizations_decision_requires_assessment_check": { + "name": "native_run_finalizations_decision_requires_assessment_check", + "value": "\"native_run_finalizations\".\"decision_id\" is null or \"native_run_finalizations\".\"assessment_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.native_run_results": { + "name": "native_run_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "caller_result_id": { + "name": "caller_result_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caller_dedupe_key": { + "name": "caller_dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_fingerprint": { + "name": "server_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_status": { + "name": "schema_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rejection_code": { + "name": "rejection_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "native_run_results_run_fingerprint_uq": { + "name": "native_run_results_run_fingerprint_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "server_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_result_uq": { + "name": "native_run_results_run_caller_result_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_result_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_dedupe_uq": { + "name": "native_run_results_run_caller_dedupe_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_results_company_id_companies_id_fk": { + "name": "native_run_results_company_id_companies_id_fk", + "tableFrom": "native_run_results", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_issue_company_fk": { + "name": "native_run_results_issue_company_fk", + "tableFrom": "native_run_results", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_run_contract_owner_fk": { + "name": "native_run_results_run_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_completion_contract_owner_fk": { + "name": "native_run_results_completion_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "native_run_results_company_issue_run_id_uq": { + "name": "native_run_results_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_automation_executions": { + "name": "pipeline_automation_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "triggering_event_id": { + "name": "triggering_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_issue_id": { + "name": "execution_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retry_of_execution_id": { + "name": "retry_of_execution_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_automation_executions_idempotency_uq": { + "name": "pipeline_automation_executions_idempotency_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "triggering_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_company_case_idx": { + "name": "pipeline_automation_executions_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_routine_idx": { + "name": "pipeline_automation_executions_routine_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_execution_issue_idx": { + "name": "pipeline_automation_executions_execution_issue_idx", + "columns": [ + { + "expression": "execution_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_retry_of_execution_idx": { + "name": "pipeline_automation_executions_retry_of_execution_idx", + "columns": [ + { + "expression": "retry_of_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_automation_executions_company_id_companies_id_fk": { + "name": "pipeline_automation_executions_company_id_companies_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_case_id_pipeline_cases_id_fk": { + "name": "pipeline_automation_executions_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_routine_id_routines_id_fk": { + "name": "pipeline_automation_executions_routine_id_routines_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_execution_issue_id_issues_id_fk": { + "name": "pipeline_automation_executions_execution_issue_id_issues_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "issues", + "columnsFrom": [ + "execution_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_automation_executions_status_check": { + "name": "pipeline_automation_executions_status_check", + "value": "\"pipeline_automation_executions\".\"status\" in ('succeeded', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_blockers": { + "name": "pipeline_case_blockers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "blocked_by_case_id": { + "name": "blocked_by_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_blockers_case_blocked_by_uq": { + "name": "pipeline_case_blockers_case_blocked_by_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_blocked_by_idx": { + "name": "pipeline_case_blockers_blocked_by_idx", + "columns": [ + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_company_case_idx": { + "name": "pipeline_case_blockers_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_blockers_company_id_companies_id_fk": { + "name": "pipeline_case_blockers_company_id_companies_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "blocked_by_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_blockers_no_self_block_check": { + "name": "pipeline_case_blockers_no_self_block_check", + "value": "\"pipeline_case_blockers\".\"case_id\" <> \"pipeline_case_blockers\".\"blocked_by_case_id\"" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_documents": { + "name": "pipeline_case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_documents_company_case_key_uq": { + "name": "pipeline_case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_document_uq": { + "name": "pipeline_case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_company_case_updated_idx": { + "name": "pipeline_case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_documents_company_id_companies_id_fk": { + "name": "pipeline_case_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_documents_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_document_id_documents_id_fk": { + "name": "pipeline_case_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_case_events": { + "name": "pipeline_case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_events_case_created_idx": { + "name": "pipeline_case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_events_company_case_idx": { + "name": "pipeline_case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_events_company_id_companies_id_fk": { + "name": "pipeline_case_events_company_id_companies_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_events_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_actor_agent_id_agents_id_fk": { + "name": "pipeline_case_events_actor_agent_id_agents_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_events_type_check": { + "name": "pipeline_case_events_type_check", + "value": "\"pipeline_case_events\".\"type\" in (\n 'ingested',\n 'updated',\n 'claimed',\n 'lease_released',\n 'lease_expired',\n 'transitioned',\n 'transition_forced',\n 'transition_suggested',\n 'suggestion_resolved',\n 'review_decided',\n 'conversation_opened',\n 'issue_linked',\n 'issue_unlinked',\n 'automation_executed',\n 'automation_failed',\n 'automation_retry_requested',\n 'automation_effects_retired',\n 'automation_retry_dispatched',\n 'blockers_set',\n 'blockers_resolved',\n 'children_terminal',\n 'upstream_drift',\n 'drift_acknowledged'\n )" + }, + "pipeline_case_events_actor_type_check": { + "name": "pipeline_case_events_actor_type_check", + "value": "\"pipeline_case_events\".\"actor_type\" in ('user', 'agent', 'system')" + }, + "pipeline_case_events_agent_run_check": { + "name": "pipeline_case_events_agent_run_check", + "value": "\"pipeline_case_events\".\"actor_type\" <> 'agent' or \"pipeline_case_events\".\"run_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_issue_links": { + "name": "pipeline_case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_issue_links_case_issue_uq": { + "name": "pipeline_case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_issue_idx": { + "name": "pipeline_case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_company_case_idx": { + "name": "pipeline_case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_automation_attempt_idx": { + "name": "pipeline_case_issue_links_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_issue_links_company_id_companies_id_fk": { + "name": "pipeline_case_issue_links_company_id_companies_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_issue_links_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_issue_id_issues_id_fk": { + "name": "pipeline_case_issue_links_issue_id_issues_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_issue_links_role_check": { + "name": "pipeline_case_issue_links_role_check", + "value": "\"pipeline_case_issue_links\".\"role\" in ('origin', 'conversation', 'work', 'automation')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_cases": { + "name": "pipeline_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_key": { + "name": "case_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "workspace_ref": { + "name": "workspace_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_case_version": { + "name": "parent_case_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pending_suggestion": { + "name": "pending_suggestion", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lease_owner_type": { + "name": "lease_owner_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_agent_id": { + "name": "lease_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_user_id": { + "name": "lease_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_kind": { + "name": "terminal_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hidden_from_board_at": { + "name": "hidden_from_board_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "child_count": { + "name": "child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "terminal_child_count": { + "name": "terminal_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_cases_pipeline_case_key_uq": { + "name": "pipeline_cases_pipeline_case_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_request_key_uq": { + "name": "pipeline_cases_parent_request_key_uq", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"pipeline_cases\".\"request_key\" is not null and \"pipeline_cases\".\"retired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_company_idx": { + "name": "pipeline_cases_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_pipeline_stage_idx": { + "name": "pipeline_cases_pipeline_stage_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_idx": { + "name": "pipeline_cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_automation_attempt_idx": { + "name": "pipeline_cases_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_retired_idx": { + "name": "pipeline_cases_retired_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retired_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_lease_expires_idx": { + "name": "pipeline_cases_lease_expires_idx", + "columns": [ + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"pipeline_cases\".\"lease_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_cases_company_id_companies_id_fk": { + "name": "pipeline_cases_company_id_companies_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_pipeline_id_pipelines_id_fk": { + "name": "pipeline_cases_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_cases_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "pipeline_cases_parent_case_id_pipeline_cases_id_fk": { + "name": "pipeline_cases_parent_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_lease_agent_id_agents_id_fk": { + "name": "pipeline_cases_lease_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "lease_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_created_by_agent_id_agents_id_fk": { + "name": "pipeline_cases_created_by_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_cases_terminal_kind_check": { + "name": "pipeline_cases_terminal_kind_check", + "value": "\"pipeline_cases\".\"terminal_kind\" is null or \"pipeline_cases\".\"terminal_kind\" in ('done', 'cancelled')" + }, + "pipeline_cases_lease_owner_type_check": { + "name": "pipeline_cases_lease_owner_type_check", + "value": "\"pipeline_cases\".\"lease_owner_type\" is null or \"pipeline_cases\".\"lease_owner_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_documents": { + "name": "pipeline_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_documents_company_pipeline_key_uq": { + "name": "pipeline_documents_company_pipeline_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_document_uq": { + "name": "pipeline_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_company_pipeline_updated_idx": { + "name": "pipeline_documents_company_pipeline_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_documents_company_id_companies_id_fk": { + "name": "pipeline_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_pipeline_id_pipelines_id_fk": { + "name": "pipeline_documents_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_document_id_documents_id_fk": { + "name": "pipeline_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_stages": { + "name": "pipeline_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_stages_pipeline_key_uq": { + "name": "pipeline_stages_pipeline_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_stages_pipeline_position_idx": { + "name": "pipeline_stages_pipeline_position_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_stages_pipeline_id_pipelines_id_fk": { + "name": "pipeline_stages_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_stages", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_stages_kind_check": { + "name": "pipeline_stages_kind_check", + "value": "\"pipeline_stages\".\"kind\" in ('working', 'review', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_transitions": { + "name": "pipeline_transitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_transitions_pipeline_edge_uq": { + "name": "pipeline_transitions_pipeline_edge_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_from_idx": { + "name": "pipeline_transitions_pipeline_from_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_to_idx": { + "name": "pipeline_transitions_pipeline_to_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_transitions_pipeline_id_pipelines_id_fk": { + "name": "pipeline_transitions_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipelines": { + "name": "pipelines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enforce_transitions": { + "name": "enforce_transitions", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipelines_company_key_uq": { + "name": "pipelines_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_idx": { + "name": "pipelines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_project_idx": { + "name": "pipelines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipelines_company_id_companies_id_fk": { + "name": "pipelines_company_id_companies_id_fk", + "tableFrom": "pipelines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipelines_project_id_projects_id_fk": { + "name": "pipelines_project_id_projects_id_fk", + "tableFrom": "pipelines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipelines_created_by_agent_id_agents_id_fk": { + "name": "pipelines_created_by_agent_id_agents_id_fk", + "tableFrom": "pipelines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_company_idx": { + "name": "plugin_config_plugin_company_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_config_company_id_companies_id_fk": { + "name": "plugin_config_company_id_companies_id_fk", + "tableFrom": "plugin_config", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_database_namespaces": { + "name": "plugin_database_namespaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_mode": { + "name": "namespace_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'schema'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_database_namespaces_plugin_idx": { + "name": "plugin_database_namespaces_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_namespace_idx": { + "name": "plugin_database_namespaces_namespace_idx", + "columns": [ + { + "expression": "namespace_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_status_idx": { + "name": "plugin_database_namespaces_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_database_namespaces_plugin_id_plugins_id_fk": { + "name": "plugin_database_namespaces_plugin_id_plugins_id_fk", + "tableFrom": "plugin_database_namespaces", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_company_idx": { + "name": "plugin_entities_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_entities_company_id_companies_id_fk": { + "name": "plugin_entities_company_id_companies_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "plugin_id", + "entity_type", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_company_idx": { + "name": "plugin_job_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_company_id_companies_id_fk": { + "name": "plugin_job_runs_company_id_companies_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_company_idx": { + "name": "plugin_logs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_logs_company_id_companies_id_fk": { + "name": "plugin_logs_company_id_companies_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_managed_resources": { + "name": "plugin_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_managed_resources_company_idx": { + "name": "plugin_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_plugin_idx": { + "name": "plugin_managed_resources_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_resource_idx": { + "name": "plugin_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_company_plugin_resource_uq": { + "name": "plugin_managed_resources_company_plugin_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_managed_resources_company_id_companies_id_fk": { + "name": "plugin_managed_resources_company_id_companies_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_managed_resources_plugin_id_plugins_id_fk": { + "name": "plugin_managed_resources_plugin_id_plugins_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_key": { + "name": "migration_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "plugin_migrations_plugin_key_idx": { + "name": "plugin_migrations_plugin_key_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_plugin_idx": { + "name": "plugin_migrations_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_status_idx": { + "name": "plugin_migrations_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_company_idx": { + "name": "plugin_webhook_deliveries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_webhook_deliveries_company_id_companies_id_fk": { + "name": "plugin_webhook_deliveries_company_id_companies_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_memberships": { + "name": "project_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_memberships_company_user_idx": { + "name": "project_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_starred_idx": { + "name": "project_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_project_idx": { + "name": "project_memberships_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_project_uq": { + "name": "project_memberships_company_user_project_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_memberships_company_id_companies_id_fk": { + "name": "project_memberships_company_id_companies_id_fk", + "tableFrom": "project_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_memberships_project_id_projects_id_fk": { + "name": "project_memberships_project_id_projects_id_fk", + "tableFrom": "project_memberships", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_documents": { + "name": "routine_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_documents_company_routine_key_uq": { + "name": "routine_documents_company_routine_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_document_uq": { + "name": "routine_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_company_routine_updated_idx": { + "name": "routine_documents_company_routine_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_documents_company_id_companies_id_fk": { + "name": "routine_documents_company_id_companies_id_fk", + "tableFrom": "routine_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routine_documents_routine_id_routines_id_fk": { + "name": "routine_documents_routine_id_routines_id_fk", + "tableFrom": "routine_documents", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_documents_document_id_documents_id_fk": { + "name": "routine_documents_document_id_documents_id_fk", + "tableFrom": "routine_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_revisions": { + "name": "routine_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_revisions_routine_revision_uq": { + "name": "routine_revisions_routine_revision_uq", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_routine_created_idx": { + "name": "routine_revisions_company_routine_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_responsible_user_idx": { + "name": "routine_revisions_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_revisions_company_id_companies_id_fk": { + "name": "routine_revisions_company_id_companies_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_routine_id_routines_id_fk": { + "name": "routine_revisions_routine_id_routines_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_restored_from_revision_id_routine_revisions_id_fk": { + "name": "routine_revisions_restored_from_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routine_revisions", + "columnsFrom": [ + "restored_from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_agent_id_agents_id_fk": { + "name": "routine_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "routine_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "routine_revision_id": { + "name": "routine_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dispatch_fingerprint": { + "name": "dispatch_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_revision_idx": { + "name": "routine_runs_revision_idx", + "columns": [ + { + "expression": "routine_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_company_responsible_user_idx": { + "name": "routine_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_dispatch_fingerprint_idx": { + "name": "routine_runs_dispatch_fingerprint_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatch_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_routine_revision_id_routine_revisions_id_fk": { + "name": "routine_runs_routine_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_revisions", + "columnsFrom": [ + "routine_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "activity_gate_policy": { + "name": "activity_gate_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "activity_gate_scope": { + "name": "activity_gate_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_folder_idx": { + "name": "routines_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_responsible_user_idx": { + "name": "routines_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_origin_idx": { + "name": "routines_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_folder_id_folders_id_fk": { + "name": "routines_folder_id_folders_id_fk", + "tableFrom": "routines", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_access_events": { + "name": "secret_access_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_owner_user_id": { + "name": "credential_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_type": { + "name": "credential_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_id": { + "name": "credential_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consumer_type": { + "name": "consumer_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumer_id": { + "name": "consumer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_access_events_company_created_idx": { + "name": "secret_access_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_secret_created_idx": { + "name": "secret_access_events_secret_created_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_user_definition_created_idx": { + "name": "secret_access_events_user_definition_created_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_company_credential_owner_idx": { + "name": "secret_access_events_company_credential_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_consumer_idx": { + "name": "secret_access_events_consumer_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_run_idx": { + "name": "secret_access_events_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_access_events_company_id_companies_id_fk": { + "name": "secret_access_events_company_id_companies_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "secret_access_events_secret_id_company_secrets_id_fk": { + "name": "secret_access_events_secret_id_company_secrets_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_issue_id_issues_id_fk": { + "name": "secret_access_events_issue_id_issues_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_plugin_id_plugins_id_fk": { + "name": "secret_access_events_plugin_id_plugins_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_run_steps": { + "name": "smoke_run_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scenario_step": { + "name": "scenario_step", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "screenshot_artifact_ref": { + "name": "screenshot_artifact_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_run_steps_company_run_idx": { + "name": "smoke_run_steps_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_run_steps_company_path_idx": { + "name": "smoke_run_steps_company_path_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_run_steps_company_id_companies_id_fk": { + "name": "smoke_run_steps_company_id_companies_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "smoke_run_steps_run_id_smoke_runs_id_fk": { + "name": "smoke_run_steps_run_id_smoke_runs_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "smoke_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_runs": { + "name": "smoke_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_runs_company_started_idx": { + "name": "smoke_runs_company_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_runs_company_status_idx": { + "name": "smoke_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_runs_company_id_companies_id_fk": { + "name": "smoke_runs_company_id_companies_id_fk", + "tableFrom": "smoke_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_card_updates": { + "name": "status_card_updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "card_id": { + "name": "card_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_issue_id": { + "name": "generation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changes": { + "name": "changes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "status_card_updates_card_started_idx": { + "name": "status_card_updates_card_started_idx", + "columns": [ + { + "expression": "card_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_card_updates_generation_issue_idx": { + "name": "status_card_updates_generation_issue_idx", + "columns": [ + { + "expression": "generation_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_card_updates_card_id_status_cards_id_fk": { + "name": "status_card_updates_card_id_status_cards_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "status_cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_card_updates_generation_issue_id_issues_id_fk": { + "name": "status_card_updates_generation_issue_id_issues_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "issues", + "columnsFrom": [ + "generation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_card_updates_run_id_heartbeat_runs_id_fk": { + "name": "status_card_updates_run_id_heartbeat_runs_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_cards": { + "name": "status_cards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title_pinned": { + "name": "title_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interest_prompt": { + "name": "interest_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queries": { + "name": "queries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "query_compiled_at": { + "name": "query_compiled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "query_compiled_by_agent_id": { + "name": "query_compiled_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_policy": { + "name": "refresh_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compiling'" + }, + "pending_change_count": { + "name": "pending_change_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pending_change_hash": { + "name": "pending_change_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_change_at": { + "name": "last_change_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "fingerprint_at": { + "name": "fingerprint_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "mentioned_issue_ids": { + "name": "mentioned_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_update_run_kind": { + "name": "last_update_run_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_eval_at": { + "name": "next_eval_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_cards_company_archived_idx": { + "name": "status_cards_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_cards_company_next_eval_idx": { + "name": "status_cards_company_next_eval_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_eval_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_cards_company_id_companies_id_fk": { + "name": "status_cards_company_id_companies_id_fk", + "tableFrom": "status_cards", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_cards_created_by_agent_id_agents_id_fk": { + "name": "status_cards_created_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_query_compiled_by_agent_id_agents_id_fk": { + "name": "status_cards_query_compiled_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "query_compiled_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_agent_id_agents_id_fk": { + "name": "status_cards_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_document_id_documents_id_fk": { + "name": "status_cards_document_id_documents_id_fk", + "tableFrom": "status_cards", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_generating_issue_id_issues_id_fk": { + "name": "status_cards_generating_issue_id_issues_id_fk", + "tableFrom": "status_cards", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_archived_by_agent_id_agents_id_fk": { + "name": "status_cards_archived_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decision_effects": { + "name": "status_decision_effects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_kind": { + "name": "effect_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_decision_effects_decision_ordinal_uq": { + "name": "status_decision_effects_decision_ordinal_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decision_effects_company_idempotency_uq": { + "name": "status_decision_effects_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decision_effects_company_id_companies_id_fk": { + "name": "status_decision_effects_company_id_companies_id_fk", + "tableFrom": "status_decision_effects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_issue_company_fk": { + "name": "status_decision_effects_issue_company_fk", + "tableFrom": "status_decision_effects", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_decision_owner_fk": { + "name": "status_decision_effects_decision_owner_fk", + "tableFrom": "status_decision_effects", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decisions": { + "name": "status_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_version": { + "name": "decision_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decision_json": { + "name": "decision_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_digest": { + "name": "decision_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "application_state": { + "name": "application_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'proposed'" + }, + "supersedes_decision_id": { + "name": "supersedes_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_decisions_company_issue_version_uq": { + "name": "status_decisions_company_issue_version_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_assessment_uq": { + "name": "status_decisions_company_assessment_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assessment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_issue_digest_uq": { + "name": "status_decisions_company_issue_digest_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decisions_company_id_companies_id_fk": { + "name": "status_decisions_company_id_companies_id_fk", + "tableFrom": "status_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_issue_company_fk": { + "name": "status_decisions_issue_company_fk", + "tableFrom": "status_decisions", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_assessment_owner_fk": { + "name": "status_decisions_assessment_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_supersedes_owner_fk": { + "name": "status_decisions_supersedes_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "status_decisions_company_issue_id_uq": { + "name": "status_decisions_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + }, + "status_decisions_company_issue_run_assessment_id_uq": { + "name": "status_decisions_company_issue_run_assessment_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summary_slots": { + "name": "summary_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_generated_by_agent_id": { + "name": "last_generated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summary_slots_document_uq": { + "name": "summary_slots_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_scope_idx": { + "name": "summary_slots_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_generating_issue_idx": { + "name": "summary_slots_company_generating_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generating_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_updated_idx": { + "name": "summary_slots_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "summary_slots_company_id_companies_id_fk": { + "name": "summary_slots_company_id_companies_id_fk", + "tableFrom": "summary_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "summary_slots_document_id_documents_id_fk": { + "name": "summary_slots_document_id_documents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_generating_issue_id_issues_id_fk": { + "name": "summary_slots_generating_issue_id_issues_id_fk", + "tableFrom": "summary_slots", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_last_generated_by_agent_id_agents_id_fk": { + "name": "summary_slots_last_generated_by_agent_id_agents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "agents", + "columnsFrom": [ + "last_generated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "summary_slots_company_scope_slot_uq": { + "name": "summary_slots_company_scope_slot_uq", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "scope_kind", + "scope_id", + "slot_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_access_audit_events": { + "name": "tool_access_audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_access_audit_company_created_idx": { + "name": "tool_access_audit_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_connection_idx": { + "name": "tool_access_audit_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_gateway_idx": { + "name": "tool_access_audit_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_access_audit_events_company_id_companies_id_fk": { + "name": "tool_access_audit_events_company_id_companies_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_connection_id_tool_connections_id_fk": { + "name": "tool_access_audit_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_requests": { + "name": "tool_action_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "canonical_arguments_hash": { + "name": "canonical_arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_arguments_summary": { + "name": "canonical_arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "signed_arguments": { + "name": "signed_arguments", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_markdown": { + "name": "preview_markdown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_agent_id": { + "name": "decided_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_action_requests_company_status_idx": { + "name": "tool_action_requests_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_invocation_idx": { + "name": "tool_action_requests_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_issue_idx": { + "name": "tool_action_requests_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_requests_company_id_companies_id_fk": { + "name": "tool_action_requests_company_id_companies_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_invocation_id_tool_invocations_id_fk": { + "name": "tool_action_requests_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_issue_id_issues_id_fk": { + "name": "tool_action_requests_issue_id_issues_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_requests_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_approval_id_approvals_id_fk": { + "name": "tool_action_requests_approval_id_approvals_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_requested_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_requested_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_resolved_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_resolved_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_decided_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_decided_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "decided_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_applications": { + "name": "tool_applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_applications_company_idx": { + "name": "tool_applications_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_status_idx": { + "name": "tool_applications_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_name_uq": { + "name": "tool_applications_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_key_uq": { + "name": "tool_applications_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_applications_company_id_companies_id_fk": { + "name": "tool_applications_company_id_companies_id_fk", + "tableFrom": "tool_applications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_applications_plugin_id_plugins_id_fk": { + "name": "tool_applications_plugin_id_plugins_id_fk", + "tableFrom": "tool_applications", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_applications_owner_agent_id_agents_id_fk": { + "name": "tool_applications_owner_agent_id_agents_id_fk", + "tableFrom": "tool_applications", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_call_events": { + "name": "tool_call_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_slot_id": { + "name": "runtime_slot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_summary": { + "name": "request_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "redaction_plan": { + "name": "redaction_plan", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rate_limit_state": { + "name": "rate_limit_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_call_events_company_created_idx": { + "name": "tool_call_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_run_idx": { + "name": "tool_call_events_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_issue_idx": { + "name": "tool_call_events_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_invocation_idx": { + "name": "tool_call_events_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_gateway_idx": { + "name": "tool_call_events_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_call_events_company_id_companies_id_fk": { + "name": "tool_call_events_company_id_companies_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_call_events_agent_id_agents_id_fk": { + "name": "tool_call_events_agent_id_agents_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_run_id_heartbeat_runs_id_fk": { + "name": "tool_call_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_issue_id_issues_id_fk": { + "name": "tool_call_events_issue_id_issues_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_call_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_application_id_tool_applications_id_fk": { + "name": "tool_call_events_application_id_tool_applications_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_connection_id_tool_connections_id_fk": { + "name": "tool_call_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_invocation_id_tool_invocations_id_fk": { + "name": "tool_call_events_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_action_request_id_tool_action_requests_id_fk": { + "name": "tool_call_events_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk": { + "name": "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_runtime_slots", + "columnsFrom": [ + "runtime_slot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_catalog_entries": { + "name": "tool_catalog_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_kind": { + "name": "entry_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "output_schema": { + "name": "output_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "annotations": { + "name": "annotations", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read'" + }, + "is_read_only": { + "name": "is_read_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_write": { + "name": "is_write", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_destructive": { + "name": "is_destructive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_hash": { + "name": "version_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_hash": { + "name": "schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_agent_id": { + "name": "reviewed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_user_id": { + "name": "reviewed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quarantined_at": { + "name": "quarantined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "quarantine_reason": { + "name": "quarantine_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_catalog_entries_company_idx": { + "name": "tool_catalog_entries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_application_idx": { + "name": "tool_catalog_entries_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_idx": { + "name": "tool_catalog_entries_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_company_status_idx": { + "name": "tool_catalog_entries_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_name_uq": { + "name": "tool_catalog_entries_connection_name_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_catalog_entries_company_id_companies_id_fk": { + "name": "tool_catalog_entries_company_id_companies_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_application_id_tool_applications_id_fk": { + "name": "tool_catalog_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_connection_id_tool_connections_id_fk": { + "name": "tool_catalog_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk": { + "name": "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "agents", + "columnsFrom": [ + "reviewed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_installs": { + "name": "tool_connection_installs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connection_installs_company_target_idx": { + "name": "tool_connection_installs_company_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_connection_idx": { + "name": "tool_connection_installs_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_target_uq": { + "name": "tool_connection_installs_target_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_installs_company_id_companies_id_fk": { + "name": "tool_connection_installs_company_id_companies_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_connection_id_tool_connections_id_fk": { + "name": "tool_connection_installs_connection_id_tool_connections_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_created_by_agent_id_agents_id_fk": { + "name": "tool_connection_installs_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "tool_connection_installs_target_type_check": { + "name": "tool_connection_installs_target_type_check", + "value": "\"tool_connection_installs\".\"target_type\" in ('company', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_connections": { + "name": "tool_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uid": { + "name": "uid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_kind": { + "name": "connection_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed'" + }, + "ownership": { + "name": "ownership", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'customer'" + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "credential_policy": { + "name": "credential_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'shared'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "transport_config": { + "name": "transport_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "credential_refs": { + "name": "credential_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_health_at": { + "name": "last_health_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_catalog_refresh_at": { + "name": "last_catalog_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connections_company_idx": { + "name": "tool_connections_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_application_idx": { + "name": "tool_connections_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_enabled_idx": { + "name": "tool_connections_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_uid_uq": { + "name": "tool_connections_company_uid_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connections_company_id_companies_id_fk": { + "name": "tool_connections_company_id_companies_id_fk", + "tableFrom": "tool_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connections_application_id_tool_applications_id_fk": { + "name": "tool_connections_application_id_tool_applications_id_fk", + "tableFrom": "tool_connections", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tool_connections_created_by_agent_id_agents_id_fk": { + "name": "tool_connections_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connections", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tool_connections_company_id_uq": { + "name": "tool_connections_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "tool_connections_ownership_check": { + "name": "tool_connections_ownership_check", + "value": "\"tool_connections\".\"ownership\" in ('platform_shared', 'platform_provisioned', 'customer', 'dcr')" + }, + "tool_connections_transport_check": { + "name": "tool_connections_transport_check", + "value": "\"tool_connections\".\"transport\" in ('mcp_remote', 'rest_api', 'local_stdio')" + }, + "tool_connections_auth_kind_check": { + "name": "tool_connections_auth_kind_check", + "value": "\"tool_connections\".\"auth_kind\" in ('oauth', 'api_key', 'none')" + }, + "tool_connections_credential_policy_check": { + "name": "tool_connections_credential_policy_check", + "value": "\"tool_connections\".\"credential_policy\" in ('shared', 'per_user', 'per_user_with_fallback')" + } + }, + "isRLSEnabled": false + }, + "public.tool_gateway_rate_limit_counters": { + "name": "tool_gateway_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_ms": { + "name": "window_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_gateway_rate_limit_counters_company_idx": { + "name": "tool_gateway_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_rate_limit_counters_window_uq": { + "name": "tool_gateway_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_gateway_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_gateway_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_gateway_sessions": { + "name": "tool_gateway_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_gateway_sessions_token_hash_uq": { + "name": "tool_gateway_sessions_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_agent_idx": { + "name": "tool_gateway_sessions_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_expires_idx": { + "name": "tool_gateway_sessions_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_run_idx": { + "name": "tool_gateway_sessions_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_issue_idx": { + "name": "tool_gateway_sessions_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_gateway_idx": { + "name": "tool_gateway_sessions_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_sessions_company_id_companies_id_fk": { + "name": "tool_gateway_sessions_company_id_companies_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_agent_id_agents_id_fk": { + "name": "tool_gateway_sessions_agent_id_agents_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_run_id_heartbeat_runs_id_fk": { + "name": "tool_gateway_sessions_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_issue_id_issues_id_fk": { + "name": "tool_gateway_sessions_issue_id_issues_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_project_id_projects_id_fk": { + "name": "tool_gateway_sessions_project_id_projects_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_invocations": { + "name": "tool_invocations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_version_hash": { + "name": "catalog_version_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "catalog_schema_hash": { + "name": "catalog_schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upstream_tool_name": { + "name": "upstream_tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "policy_decision": { + "name": "policy_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "approval_state": { + "name": "approval_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_required'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "upstream_request_id": { + "name": "upstream_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "result_artifact_id": { + "name": "result_artifact_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_invocations_company_created_idx": { + "name": "tool_invocations_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_run_idx": { + "name": "tool_invocations_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_issue_idx": { + "name": "tool_invocations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_gateway_idx": { + "name": "tool_invocations_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_company_idempotency_uq": { + "name": "tool_invocations_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_invocations_company_id_companies_id_fk": { + "name": "tool_invocations_company_id_companies_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_invocations_agent_id_agents_id_fk": { + "name": "tool_invocations_agent_id_agents_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_issue_id_issues_id_fk": { + "name": "tool_invocations_issue_id_issues_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_run_id_heartbeat_runs_id_fk": { + "name": "tool_invocations_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_invocations_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_application_id_tool_applications_id_fk": { + "name": "tool_invocations_application_id_tool_applications_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_connection_id_tool_connections_id_fk": { + "name": "tool_invocations_connection_id_tool_connections_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateway_tokens": { + "name": "tool_mcp_gateway_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_client'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_label": { + "name": "client_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "owner_note": { + "name": "owner_note", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "allowed_actions": { + "name": "allowed_actions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"tools/list\",\"tools/call\"]'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expiry_override_reason": { + "name": "expiry_override_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_user_id": { + "name": "expiry_override_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_agent_id": { + "name": "expiry_override_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expiry_override_at": { + "name": "expiry_override_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateway_tokens_token_hash_uq": { + "name": "tool_mcp_gateway_tokens_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_gateway_idx": { + "name": "tool_mcp_gateway_tokens_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_subject_idx": { + "name": "tool_mcp_gateway_tokens_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_company_expires_idx": { + "name": "tool_mcp_gateway_tokens_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateway_tokens_company_id_companies_id_fk": { + "name": "tool_mcp_gateway_tokens_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "expiry_override_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateways": { + "name": "tool_mcp_gateways", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gw_' || replace(gen_random_uuid()::text, '-', '')" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_slug": { + "name": "display_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "default_profile_mode": { + "name": "default_profile_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_only'" + }, + "context_scope_type": { + "name": "context_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "context_scope_id": { + "name": "context_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_issue_id": { + "name": "approval_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "auth_config": { + "name": "auth_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"bearer\":{\"enabled\":true,\"tokenPrefix\":\"pcgw\",\"defaultTtlSeconds\":7776000,\"requireFiniteExpiry\":true,\"longLivedTokenRequiresOverride\":true},\"oauth\":{\"enabled\":false,\"reservedFor\":\"v1_5\",\"dynamicClientRegistration\":false,\"authorizationCodePkce\":false}}'::jsonb" + }, + "header_policy": { + "name": "header_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"callerPassthrough\":{\"enabled\":false,\"allowedHeaders\":[]},\"staticHeaders\":[],\"generatedMetadata\":{\"enabled\":false,\"allowedHeaders\":[]},\"responseHeaders\":{\"forwardMcpRequiredHeaders\":true,\"forwardSafeCacheHeaders\":true}}'::jsonb" + }, + "metadata_policy": { + "name": "metadata_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"forwardCompanyId\":false,\"forwardGatewayId\":false,\"forwardProjectId\":false,\"forwardIssueId\":false,\"forwardAgentId\":false,\"forwardRunId\":false,\"forwardCorrelationId\":true}'::jsonb" + }, + "on_demand_tools_config": { + "name": "on_demand_tools_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"enabled\":false,\"searchToolName\":\"search_tools\",\"runToolName\":\"run_tool\"}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateways_company_idx": { + "name": "tool_mcp_gateways_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_status_idx": { + "name": "tool_mcp_gateways_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_profile_idx": { + "name": "tool_mcp_gateways_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_public_id_uq": { + "name": "tool_mcp_gateways_public_id_uq", + "columns": [ + { + "expression": "gateway_public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_slug_uq": { + "name": "tool_mcp_gateways_company_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_name_uq": { + "name": "tool_mcp_gateways_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateways_company_id_companies_id_fk": { + "name": "tool_mcp_gateways_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateways_profile_id_tool_profiles_id_fk": { + "name": "tool_mcp_gateways_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "tool_mcp_gateways_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_project_id_projects_id_fk": { + "name": "tool_mcp_gateways_project_id_projects_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_approval_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_approval_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "approval_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_oauth_states": { + "name": "tool_oauth_states", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_session_id": { + "name": "created_by_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_scopes": { + "name": "requested_scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "return_to": { + "name": "return_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_oauth_states_company_idx": { + "name": "tool_oauth_states_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_connection_idx": { + "name": "tool_oauth_states_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_actor_idx": { + "name": "tool_oauth_states_actor_idx", + "columns": [ + { + "expression": "created_by_actor_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_expires_at_idx": { + "name": "tool_oauth_states_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_oauth_states_company_id_companies_id_fk": { + "name": "tool_oauth_states_company_id_companies_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_connection_id_tool_connections_id_fk": { + "name": "tool_oauth_states_connection_id_tool_connections_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_policies": { + "name": "tool_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_type": { + "name": "policy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "selectors": { + "name": "selectors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_policies_company_enabled_idx": { + "name": "tool_policies_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_type_idx": { + "name": "tool_policies_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_name_uq": { + "name": "tool_policies_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_policies_company_id_companies_id_fk": { + "name": "tool_policies_company_id_companies_id_fk", + "tableFrom": "tool_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_policies_created_by_agent_id_agents_id_fk": { + "name": "tool_policies_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_policies", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_bindings": { + "name": "tool_profile_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_bindings_company_target_idx": { + "name": "tool_profile_bindings_company_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_bindings_target_profile_uq": { + "name": "tool_profile_bindings_target_profile_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_bindings_company_id_companies_id_fk": { + "name": "tool_profile_bindings_company_id_companies_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_bindings_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_created_by_agent_id_agents_id_fk": { + "name": "tool_profile_bindings_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_entries": { + "name": "tool_profile_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "selector_type": { + "name": "selector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'include'" + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_entries_company_profile_idx": { + "name": "tool_profile_entries_company_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_application_idx": { + "name": "tool_profile_entries_application_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_connection_idx": { + "name": "tool_profile_entries_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_catalog_entry_idx": { + "name": "tool_profile_entries_catalog_entry_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "catalog_entry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_entries_company_id_companies_id_fk": { + "name": "tool_profile_entries_company_id_companies_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_entries_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_application_id_tool_applications_id_fk": { + "name": "tool_profile_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_connection_id_tool_connections_id_fk": { + "name": "tool_profile_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profiles": { + "name": "tool_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "default_action": { + "name": "default_action", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'deny'" + }, + "new_tools_reviewed_at": { + "name": "new_tools_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profiles_company_status_idx": { + "name": "tool_profiles_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_key_uq": { + "name": "tool_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_name_uq": { + "name": "tool_profiles_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profiles_company_id_companies_id_fk": { + "name": "tool_profiles_company_id_companies_id_fk", + "tableFrom": "tool_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_rate_limit_counters": { + "name": "tool_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_rate_limit_counters_company_idx": { + "name": "tool_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_rate_limit_counters_window_uq": { + "name": "tool_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_rate_limit_counters_policy_id_tool_policies_id_fk": { + "name": "tool_rate_limit_counters_policy_id_tool_policies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "tool_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_metric_counters": { + "name": "tool_runtime_metric_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket_start_at": { + "name": "bucket_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_metric_counters_company_metric_idx": { + "name": "tool_runtime_metric_counters_company_metric_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_metric_counters_bucket_uq": { + "name": "tool_runtime_metric_counters_bucket_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_metric_counters_company_id_companies_id_fk": { + "name": "tool_runtime_metric_counters_company_id_companies_id_fk", + "tableFrom": "tool_runtime_metric_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_slots": { + "name": "tool_runtime_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_scope_type": { + "name": "owner_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connection'" + }, + "owner_scope_id": { + "name": "owner_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_kind": { + "name": "runtime_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_stdio'" + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'stopped'" + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_scope": { + "name": "workspace_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_scope_hash": { + "name": "credential_scope_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_id": { + "name": "process_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "command_template_key": { + "name": "command_template_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_health_check_at": { + "name": "last_health_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_expires_at": { + "name": "idle_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_deadline_at": { + "name": "idle_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_slots_company_idx": { + "name": "tool_runtime_slots_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_connection_idx": { + "name": "tool_runtime_slots_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_execution_workspace_idx": { + "name": "tool_runtime_slots_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_slot_key_uq": { + "name": "tool_runtime_slots_slot_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_slots_company_id_companies_id_fk": { + "name": "tool_runtime_slots_company_id_companies_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_application_id_tool_applications_id_fk": { + "name": "tool_runtime_slots_application_id_tool_applications_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_connection_id_tool_connections_id_fk": { + "name": "tool_runtime_slots_connection_id_tool_connections_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk": { + "name": "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk": { + "name": "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_issue_id_issues_id_fk": { + "name": "tool_runtime_slots_issue_id_issues_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_stdio_command_templates": { + "name": "tool_stdio_command_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_key": { + "name": "template_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env_keys": { + "name": "env_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_stdio_command_templates_company_idx": { + "name": "tool_stdio_command_templates_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_status_idx": { + "name": "tool_stdio_command_templates_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_key_uq": { + "name": "tool_stdio_command_templates_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_stdio_command_templates_company_id_companies_id_fk": { + "name": "tool_stdio_command_templates_company_id_companies_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_stdio_command_templates_created_by_agent_id_agents_id_fk": { + "name": "tool_stdio_command_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_inbox_agent_policies": { + "name": "user_inbox_agent_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "allowed_agent_ids": { + "name": "allowed_agent_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_inbox_agent_policies_company_user_uq": { + "name": "user_inbox_agent_policies_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_inbox_agent_policies_allowed_agent_ids_idx": { + "name": "user_inbox_agent_policies_allowed_agent_ids_idx", + "columns": [ + { + "expression": "allowed_agent_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "user_inbox_agent_policies_company_id_companies_id_fk": { + "name": "user_inbox_agent_policies_company_id_companies_id_fk", + "tableFrom": "user_inbox_agent_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_inbox_agent_policies_mode_check": { + "name": "user_inbox_agent_policies_mode_check", + "value": "\"user_inbox_agent_policies\".\"mode\" in ('open', 'allowlist', 'disabled')" + } + }, + "isRLSEnabled": false + }, + "public.user_secret_declarations": { + "name": "user_secret_declarations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_missing_override": { + "name": "allow_missing_override", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_secret_declarations_company_idx": { + "name": "user_secret_declarations_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_definition_idx": { + "name": "user_secret_declarations_definition_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_idx": { + "name": "user_secret_declarations_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_company_required_idx": { + "name": "user_secret_declarations_company_required_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "required", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_path_uq": { + "name": "user_secret_declarations_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_required_override_idx": { + "name": "user_secret_declarations_required_override_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "allow_missing_override", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_secret_declarations\".\"allow_missing_override\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_declarations_company_id_companies_id_fk": { + "name": "user_secret_declarations_company_id_companies_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_secret_definitions": { + "name": "user_secret_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_guidance": { + "name": "usage_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_secret_definitions_company_status_idx": { + "name": "user_secret_definitions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_provider_idx": { + "name": "user_secret_definitions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_provider_config_idx": { + "name": "user_secret_definitions_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_key_uq": { + "name": "user_secret_definitions_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_secret_definitions\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_definitions_company_id_companies_id_fk": { + "name": "user_secret_definitions_company_id_companies_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_created_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_created_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_updated_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_updated_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_sidebar_preferences": { + "name": "user_sidebar_preferences", + "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 + }, + "company_order": { + "name": "company_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_sidebar_preferences_user_uq": { + "name": "user_sidebar_preferences_user_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_assessments": { + "name": "work_assessments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contract_id": { + "name": "contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_kind": { + "name": "trigger_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_ref": { + "name": "trigger_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_capability": { + "name": "trigger_capability", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_actor_company_id": { + "name": "trigger_actor_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prior_issue_status": { + "name": "prior_issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prior_status_version": { + "name": "prior_status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "prior_decision_id": { + "name": "prior_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assessment_json": { + "name": "assessment_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "input_digest": { + "name": "input_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "supersedes_assessment_id": { + "name": "supersedes_assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_assessments_company_issue_input_uq": { + "name": "work_assessments_company_issue_input_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "work_assessments_company_id_companies_id_fk": { + "name": "work_assessments_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_trigger_actor_company_id_companies_id_fk": { + "name": "work_assessments_trigger_actor_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "trigger_actor_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_issue_company_fk": { + "name": "work_assessments_issue_company_fk", + "tableFrom": "work_assessments", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_run_owner_fk": { + "name": "work_assessments_run_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_contract_owner_fk": { + "name": "work_assessments_contract_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_result_owner_fk": { + "name": "work_assessments_result_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_supersedes_owner_fk": { + "name": "work_assessments_supersedes_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "supersedes_assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "work_assessments_company_issue_run_id_uq": { + "name": "work_assessments_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "work_assessments_trigger_actor_company_check": { + "name": "work_assessments_trigger_actor_company_check", + "value": "\"work_assessments\".\"trigger_actor_company_id\" = \"work_assessments\".\"company_id\"" + } + }, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_issue_started_idx": { + "name": "workspace_operations_company_workspace_issue_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_issue_id_issues_id_fk": { + "name": "workspace_operations_issue_id_issues_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure_handle": { + "name": "exposure_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backend_url": { + "name": "backend_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_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": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 958d84d100..9ed12fd794 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -1611,6 +1611,13 @@ "when": 1787922567272, "tag": "0231_remove_app_connection_wide_includes", "breakpoints": true + }, + { + "idx": 232, + "version": "7", + "when": 1787922607497, + "tag": "0232_fixed_hannibal_king", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index fcb6587f49..e93be28672 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -137,6 +137,8 @@ export { toolApplications, toolConnections, connectionGrants, + connectionGrantMembers, + connectionGrantDelegations, toolConnectionInstalls, toolOauthStates, toolCatalogEntries, diff --git a/packages/db/src/schema/issue_thread_interactions.ts b/packages/db/src/schema/issue_thread_interactions.ts index 54305983ea..df056c84f0 100644 --- a/packages/db/src/schema/issue_thread_interactions.ts +++ b/packages/db/src/schema/issue_thread_interactions.ts @@ -45,6 +45,7 @@ export const issueThreadInteractions = pgTable( summary: text("summary"), createdByAgentId: uuid("created_by_agent_id").references(() => agents.id), addresseeAgentId: uuid("addressee_agent_id").references(() => agents.id, { onDelete: "set null" }), + addresseeUserId: text("addressee_user_id"), createdByUserId: text("created_by_user_id"), resolvedByAgentId: uuid("resolved_by_agent_id").references(() => agents.id), resolvedByRunId: uuid("resolved_by_run_id").references(() => heartbeatRuns.id, { onDelete: "set null" }), @@ -72,5 +73,6 @@ export const issueThreadInteractions = pgTable( .where(sql`${table.idempotencyKey} IS NOT NULL`), sourceCommentIdx: index("issue_thread_interactions_source_comment_idx").on(table.sourceCommentId), addresseeAgentIdx: index("issue_thread_interactions_addressee_agent_idx").on(table.addresseeAgentId), + addresseeUserIdx: index("issue_thread_interactions_addressee_user_idx").on(table.addresseeUserId), }), ); diff --git a/packages/db/src/schema/tool_access.ts b/packages/db/src/schema/tool_access.ts index 01661b6344..9db61e1faa 100644 --- a/packages/db/src/schema/tool_access.ts +++ b/packages/db/src/schema/tool_access.ts @@ -29,11 +29,13 @@ import type { ToolConnectionHealthStatus, ToolConnectionAuthKind, ToolConnectionKind, + ToolConnectionCredentialPolicy, ToolConnectionOwnership, ToolConnectionInstallTargetType, ToolConnectionStatus, ToolConnectionTransport, ConnectionGrantKind, + ConnectionGrantMemberSubjectType, ConnectionGrantStatus, ToolCredentialSecretRef, ToolInvocationApprovalState, @@ -117,6 +119,7 @@ export const toolConnections = pgTable( ownership: text("ownership").$type().notNull().default("customer"), transport: text("transport").$type().notNull(), authKind: text("auth_kind").$type().notNull().default("none"), + credentialPolicy: text("credential_policy").$type().notNull().default("shared"), status: text("status").$type().notNull().default("draft"), enabled: boolean("enabled").notNull().default(false), config: jsonb("config").$type>().notNull().default({}), @@ -138,6 +141,7 @@ export const toolConnections = pgTable( 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')`), + check("tool_connections_credential_policy_check", sql`${table.credentialPolicy} in ('shared', 'per_user', 'per_user_with_fallback')`), 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), @@ -168,10 +172,10 @@ export const connectionGrants = pgTable( updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), }, (table) => [ - check("connection_grants_kind_check", sql`${table.kind} in ('workspace', 'user')`), + check("connection_grants_kind_check", sql`${table.kind} in ('organization', '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'`), + check("connection_grants_subject_check", sql`(${table.kind} = 'user' and ${table.subjectUserId} is not null) or (${table.kind} = 'organization' and ${table.subjectUserId} is null)`), + check("connection_grants_default_check", sql`${table.isDefault} = false or ${table.kind} = 'organization'`), foreignKey({ columns: [table.companyId, table.connectionId], foreignColumns: [toolConnections.companyId, toolConnections.id], @@ -179,8 +183,52 @@ export const connectionGrants = pgTable( }).onDelete("cascade"), index("connection_grants_company_connection_idx").on(table.companyId, table.connectionId), index("connection_grants_subject_user_idx").on(table.companyId, table.subjectUserId), + unique("connection_grants_company_id_uq").on(table.companyId, table.id), 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'`), + uniqueIndex("connection_grants_default_uq").on(table.connectionId).where(sql`${table.isDefault} = true and ${table.kind} = 'organization'`), + ], +); + +export const connectionGrantMembers = pgTable( + "connection_grant_members", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }), + grantId: uuid("grant_id").notNull(), + subjectType: text("subject_type").$type().notNull(), + subjectId: text("subject_id").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => [ + check("connection_grant_members_subject_type_check", sql`${table.subjectType} in ('user')`), + foreignKey({ + columns: [table.companyId, table.grantId], + foreignColumns: [connectionGrants.companyId, connectionGrants.id], + name: "connection_grant_members_company_grant_fk", + }).onDelete("cascade"), + index("connection_grant_members_company_subject_idx").on(table.companyId, table.subjectType, table.subjectId), + uniqueIndex("connection_grant_members_grant_subject_uq").on(table.grantId, table.subjectType, table.subjectId), + ], +); + +export const connectionGrantDelegations = pgTable( + "connection_grant_delegations", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }), + grantId: uuid("grant_id").notNull(), + agentId: uuid("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }), + createdByUserId: text("created_by_user_id").notNull(), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => [ + foreignKey({ + columns: [table.companyId, table.grantId], + foreignColumns: [connectionGrants.companyId, connectionGrants.id], + name: "connection_grant_delegations_company_grant_fk", + }).onDelete("cascade"), + index("connection_grant_delegations_company_agent_idx").on(table.companyId, table.agentId), + uniqueIndex("connection_grant_delegations_grant_agent_uq").on(table.grantId, table.agentId), ], ); diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 2c73db8439..29f316dff2 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1106,6 +1106,7 @@ export type { RequestConfirmationResult, RequestConfirmationToolActionPayload, RequestConfirmationToolActionResult, + RequestConfirmationConnectionAuthorizationPayload, RequestConfirmationSecretProposalPayload, RequestConfirmationSecretProposalResult, RequestCheckboxConfirmationOption, @@ -1344,6 +1345,7 @@ export type { ToolCatalogEntryKind, ToolConnectionHealthStatus, ToolConnectionAuthKind, + ToolConnectionCredentialPolicy, ToolConnectionOwnership, ToolConnectionTransport, ToolConnectionStatus, @@ -1370,9 +1372,17 @@ export type { ToolConnectionInstallTargetType, ToolConnectionRemovalResult, ToolConnectionRemovalSummary, + ConnectionAudienceMember, ConnectionGrant, + ConnectionGrantCapabilities, + ConnectionGrantDelegation, ConnectionGrantKind, + ConnectionGrantMember, + ConnectionGrantMemberSubjectType, + ConnectionGrantsResponse, ConnectionGrantStatus, + ToolConnectionCapabilities, + ToolConnectionCreateCapabilities, ConnectionTokenScope, ConnectionTokenRequest, ConnectionTokenAttribution, @@ -2070,6 +2080,8 @@ export { connectionTokenRequestSchema, connectionTokenSubjectSchema, startConnectionAuthorizationSchema, + createConnectionGrantDelegationSchema, + replaceConnectionGrantMembersSchema, toolConnectionTestCallSchema, toolPolicyTestRequestSchema, importMcpJsonSchema, @@ -2156,6 +2168,8 @@ export { type PutToolConnectionInstalls, type UpdateToolMcpGateway, type ConnectionTokenRequestInput, + type CreateConnectionGrantDelegation, + type ReplaceConnectionGrantMembersInput, type ImportMcpJson, type ToolPolicyTestRequestInput, type CreateToolInvocation, diff --git a/packages/shared/src/types/attention.ts b/packages/shared/src/types/attention.ts index 1994c49abc..d61c7acc79 100644 --- a/packages/shared/src/types/attention.ts +++ b/packages/shared/src/types/attention.ts @@ -207,7 +207,9 @@ export interface AttentionResolverAudience { resolverPolicyProvenance: IssueThreadInteractionResolverPolicyProvenance; /** Agent the card is addressed to, when it names one. */ addresseeAgentId: string | null; - /** Display name of {@link addresseeAgentId}, resolved server-side. */ + /** User the card is addressed to, when it names one. */ + addresseeUserId?: string | null; + /** Display name of the agent addressee, resolved server-side. */ addresseeName: string | null; /** Agent that created the card, excluded when the policy is `not_creator`. */ createdByAgentId: string | null; diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 508370c990..ba9a7ea78c 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -498,13 +498,22 @@ export type { ToolConnection, ToolConnectionHealthStatus, ToolConnectionAuthKind, + ToolConnectionCredentialPolicy, ToolConnectionOwnership, ToolConnectionTransport, ToolConnectionStatus, ToolConnectionKind, + ConnectionAudienceMember, ConnectionGrant, + ConnectionGrantCapabilities, + ConnectionGrantDelegation, ConnectionGrantKind, + ConnectionGrantMember, + ConnectionGrantMemberSubjectType, + ConnectionGrantsResponse, ConnectionGrantStatus, + ToolConnectionCapabilities, + ToolConnectionCreateCapabilities, ToolCredentialSecretRef, ToolInvocation, ToolInvocationApprovalState, @@ -711,6 +720,7 @@ export type { RequestConfirmationResult, RequestConfirmationToolActionPayload, RequestConfirmationToolActionResult, + RequestConfirmationConnectionAuthorizationPayload, RequestConfirmationSecretProposalPayload, RequestConfirmationSecretProposalResult, RequestCheckboxConfirmationOption, diff --git a/packages/shared/src/types/issue.ts b/packages/shared/src/types/issue.ts index 29f69691e6..042e24bf0d 100644 --- a/packages/shared/src/types/issue.ts +++ b/packages/shared/src/types/issue.ts @@ -1268,6 +1268,23 @@ export interface RequestConfirmationSecretProposalResult { updatedAt: string; } +/** + * Presentation metadata for a connection-authorization confirmation + * (PAP-17835). The interaction kind and the server-addressed audience are + * unchanged; this block only lets the card render "Connect your Gmail to + * continue" and name the agent that is waiting, instead of parsing a magic + * title string to work out what the card is about. + */ +export interface RequestConfirmationConnectionAuthorizationPayload { + version: 1; + /** Provider label for the copy, e.g. "Gmail". Never a secret name or ref. */ + providerName: string; + /** The connection's display name, when it differs from the provider. */ + connectionName?: string | null; + /** The agent whose work is blocked, for " needs your identity". */ + requestingAgentName?: string | null; +} + export interface RequestConfirmationPayload { version: 1; prompt: string; @@ -1282,6 +1299,7 @@ export interface RequestConfirmationPayload { target?: RequestConfirmationTarget | null; toolAction?: RequestConfirmationToolActionPayload; secretProposal?: RequestConfirmationSecretProposalPayload; + connectionAuthorization?: RequestConfirmationConnectionAuthorizationPayload; } export interface RequestCheckboxConfirmationOption { @@ -1395,6 +1413,7 @@ export interface IssueThreadInteractionBase extends IssueThreadInteractionActorF sourceCommentId?: string | null; sourceRunId?: string | null; addresseeAgentId?: string | null; + addresseeUserId?: string | null; title?: string | null; summary?: string | null; status: IssueThreadInteractionStatus; diff --git a/packages/shared/src/types/tool-access.ts b/packages/shared/src/types/tool-access.ts index 6bbcffcc69..406a9a73a9 100644 --- a/packages/shared/src/types/tool-access.ts +++ b/packages/shared/src/types/tool-access.ts @@ -72,8 +72,10 @@ export type ToolConnectionAuthKind = "oauth" | "api_key" | "none"; export type ToolConnectionOwnership = "platform_shared" | "platform_provisioned" | "customer" | "dcr"; export type ToolConnectionStatus = "draft" | "active" | "disabled" | "archived"; export type ToolConnectionInstallTargetType = "company" | "agent"; -export type ConnectionGrantKind = "workspace" | "user"; +export type ConnectionGrantKind = "organization" | "user"; export type ConnectionGrantStatus = "active" | "revoked" | "expired" | "needs_reauthorization"; +export type ToolConnectionCredentialPolicy = "shared" | "per_user" | "per_user_with_fallback"; +export type ConnectionGrantMemberSubjectType = "user"; export type ToolCredentialPlacement = "header" | "env"; export interface McpConnectionCredentialRef { @@ -132,6 +134,7 @@ export interface ToolConnection { ownership: ToolConnectionOwnership; transport: ToolConnectionTransport; authKind: ToolConnectionAuthKind; + credentialPolicy: ToolConnectionCredentialPolicy; status?: ToolConnectionStatus; transportConfig: Record; config?: Record; @@ -172,6 +175,78 @@ export interface ConnectionGrant { lastUsedAt: Date | null; createdAt: Date; updatedAt: Date; + members?: ConnectionGrantMember[]; + delegations?: ConnectionGrantDelegation[]; + /** + * Server-computed authorization for this grant (PAP-17835). The UI renders the + * §3 permission matrix from these booleans; it must never rebuild policy from + * `membershipRole` strings, because grant authorization also depends on + * creator/subject identity that the client cannot evaluate. + */ + capabilities?: ConnectionGrantCapabilities; +} + +export interface ConnectionGrantCapabilities { + canRevoke: boolean; + canEditAudience: boolean; +} + +/** + * Connection-level capabilities for the personal-connections UX. Policy-forbidden + * actions are omitted from the UI entirely; `false` here means "do not render", + * not "render disabled". + */ +export interface ToolConnectionCapabilities { + canConfigure: boolean; + canCreateOrganizationGrant: boolean; + canSetCompanyInstall: boolean; + canConnectAsCurrentUser: boolean; + canManageAgentInstalls: boolean; + canViewOtherPersonalIdentities: boolean; + editableAgentIds: string[]; +} + +/** + * Capabilities needed before a tool connection exists. These are returned by + * the company-scoped app gallery read so create flows do not have to infer + * authorization from membership roles or wait for a connection id. + */ +export interface ToolConnectionCreateCapabilities { + canSetCompanyInstall: boolean; + companyInstallReason: string | null; +} + +export interface ConnectionGrantsResponse { + connection: { id: string; uid: string }; + grants: ConnectionGrant[]; + capabilities: ToolConnectionCapabilities; + currentUserId: string | null; + members: ConnectionAudienceMember[]; +} + +/** A company member that can appear in an organization grant's audience. */ +export interface ConnectionAudienceMember { + userId: string; + name: string | null; + email: string | null; +} + +export interface ConnectionGrantDelegation { + id: string; + companyId: string; + grantId: string; + agentId: string; + createdByUserId: string; + createdAt: Date; +} + +export interface ConnectionGrantMember { + id: string; + companyId: string; + grantId: string; + subjectType: ConnectionGrantMemberSubjectType; + subjectId: string; + createdAt: Date; } export interface ToolConnectionInstall { @@ -240,11 +315,14 @@ export type ConnectionTokenSubject = { type: "app" } | { type: "user"; userId: s export const CONNECTION_RECOVERABLE_ERROR_CODES = [ "user_authorization_required", + "organization_authorization_required", + "grant_audience_denied", "grant_revoked", "needs_reauthorization", "installation_required", "connection_not_installed", "subject_not_permitted", + "standing_delegation_required", ] as const; export type ConnectionRecoverableErrorCode = typeof CONNECTION_RECOVERABLE_ERROR_CODES[number]; diff --git a/packages/shared/src/validators/index.ts b/packages/shared/src/validators/index.ts index dceb763f3d..473a9b09c5 100644 --- a/packages/shared/src/validators/index.ts +++ b/packages/shared/src/validators/index.ts @@ -868,6 +868,8 @@ export { connectionTokenScopeSchema, connectionTokenSubjectSchema, startConnectionAuthorizationSchema, + createConnectionGrantDelegationSchema, + replaceConnectionGrantMembersSchema, createToolTrustRuleFromActionRequestSchema, revokeToolTrustRuleSchema, toolPolicyTestRequestSchema, @@ -945,6 +947,8 @@ export { type UnbindToolProfileBinding, type UpsertToolCatalogEntry, type ConnectionTokenRequestInput, + type CreateConnectionGrantDelegation, + type ReplaceConnectionGrantMembersInput, type ToolPolicyTestRequestInput, type CreateToolTrustRuleFromActionRequest, type RevokeToolTrustRule, diff --git a/packages/shared/src/validators/issue.ts b/packages/shared/src/validators/issue.ts index 8b4313ff6c..f880049694 100644 --- a/packages/shared/src/validators/issue.ts +++ b/packages/shared/src/validators/issue.ts @@ -1334,6 +1334,7 @@ export const requestItemVerdictsResultSchema = z.object({ const createIssueThreadInteractionCommon = { resolverPolicy: issueThreadInteractionResolverPolicySchema.optional(), addresseeAgentId: z.string().guid().nullable().optional(), + addresseeUserId: z.string().trim().min(1).nullable().optional(), }; export const createIssueThreadInteractionSchema = z.discriminatedUnion("kind", [ diff --git a/packages/shared/src/validators/tool-access.ts b/packages/shared/src/validators/tool-access.ts index b7297ac7a5..ee293f238e 100644 --- a/packages/shared/src/validators/tool-access.ts +++ b/packages/shared/src/validators/tool-access.ts @@ -44,8 +44,13 @@ export const toolApplicationStatusSchema = z.enum(TOOL_APPLICATION_STATUSES); export const toolConnectionTransportSchema = z.enum(["mcp_remote", "rest_api", "local_stdio"]); export const toolConnectionAuthKindSchema = z.enum(["oauth", "api_key", "none"]); export const toolConnectionOwnershipSchema = z.enum(["platform_shared", "platform_provisioned", "customer", "dcr"]); -export const connectionGrantKindSchema = z.enum(["workspace", "user"]); +export const connectionGrantKindSchema = z.enum(["organization", "user"]); export const connectionGrantStatusSchema = z.enum(["active", "revoked", "expired", "needs_reauthorization"]); +export const createConnectionGrantDelegationSchema = z.object({ + agentId: z.string().uuid(), +}); +export type CreateConnectionGrantDelegation = z.infer; +export const toolConnectionCredentialPolicySchema = z.enum(["shared", "per_user", "per_user_with_fallback"]); export const toolConnectionStatusSchema = z.enum(["draft", "active", "disabled", "archived"]); export const toolConnectionInstallTargetTypeSchema = z.enum(["company", "agent"]); export const toolCredentialPlacementSchema = z.enum(["header", "env"]); @@ -155,6 +160,7 @@ export const createToolConnectionSchema = z.object({ name: z.string().trim().min(1).max(160), transport: toolConnectionTransportSchema.optional(), authKind: toolConnectionAuthKindSchema.default("none"), + credentialPolicy: toolConnectionCredentialPolicySchema.optional(), ownership: toolConnectionOwnershipSchema.default("customer"), status: toolConnectionStatusSchema.optional(), connectionKind: toolConnectionKindSchema.default("managed"), @@ -197,7 +203,7 @@ export const connectionGrantSchema = z.object({ updatedAt: z.coerce.date(), }).superRefine((grant, ctx) => { if ((grant.kind === "user") !== Boolean(grant.subjectUserId)) { - ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["subjectUserId"], message: "User grants require a subject user; workspace grants must not have one" }); + ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["subjectUserId"], message: "User grants require a subject user; organization grants must not have one" }); } }); @@ -210,6 +216,18 @@ export const putToolConnectionInstallsSchema = z.object({ export type PutToolConnectionInstalls = z.infer; +/** + * Audience replacement for an organization grant (PAP-17835). An empty array is + * the canonical encoding of "all organization members" — the UI never exposes + * "empty list" as the mental model, so the wire format carries the emptiness and + * the copy layer translates it. + */ +export const replaceConnectionGrantMembersSchema = z.object({ + memberUserIds: z.array(z.string().trim().min(1).max(500)).max(1000), +}).strict(); + +export type ReplaceConnectionGrantMembersInput = z.infer; + export const connectionTokenIssuancePathSchema = z.enum(CONNECTION_TOKEN_ISSUANCE_PATHS); export const connectionTokenScopeSchema = z.union([ @@ -342,6 +360,13 @@ export const connectToolAppSchema = z.object({ applicationId: z.string().guid().optional(), authMode: genericMcpAuthModeSchema.optional(), oauthClient: genericMcpOAuthClientSchema.optional(), + /** + * Which identity this credential becomes (PAP-17835). `user` means "Just me": + * the credential is committed to the caller's own personal grant and never to + * the connection row's shared secret refs or the default organization grant. + * Omitted keeps the historical shared-credential behaviour. + */ + grantKind: connectionGrantKindSchema.optional(), }).superRefine((value, ctx) => { if (value.configValues) rejectSensitiveConfigKeys(value.configValues, ctx, ["configValues"]); if (value.credentialValues) rejectUnsafeHeaderCredentials(value.credentialValues, ctx, ["credentialValues"]); diff --git a/server/src/__tests__/access-routes-permissions-upgrade.test.ts b/server/src/__tests__/access-routes-permissions-upgrade.test.ts index b7180581a5..7f9d440cfe 100644 --- a/server/src/__tests__/access-routes-permissions-upgrade.test.ts +++ b/server/src/__tests__/access-routes-permissions-upgrade.test.ts @@ -5,10 +5,15 @@ import { and, eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { activityLog, + agents, companies, companyMemberships, + connectionGrantDelegations, + connectionGrants, createDb, principalPermissionGrants, + toolApplications, + toolConnections, } from "@paperclipai/db"; import { getEmbeddedPostgresTestSupport, @@ -90,8 +95,13 @@ describeEmbeddedPostgres("access routes permissions upgrade compatibility", () = afterEach(async () => { await db.delete(activityLog); + await db.delete(connectionGrantDelegations); + await db.delete(connectionGrants); + await db.delete(toolConnections); + await db.delete(toolApplications); await db.delete(principalPermissionGrants); await db.delete(companyMemberships); + await db.delete(agents); await db.delete(companies); }); @@ -164,4 +174,85 @@ describeEmbeddedPostgres("access routes permissions upgrade compatibility", () = grantedByUserId: owner.principalId, }); }); + + it("sweeps personal connection access when the member route suspends a user", async () => { + const { company, owner } = await createCompanyWithOwner(db); + const member = await db.insert(companyMemberships).values({ + companyId: company.id, + principalType: "user", + principalId: `member-${randomUUID()}`, + status: "active", + membershipRole: "member", + }).returning().then((rows) => rows[0]!); + const agent = await db.insert(agents).values({ + companyId: company.id, + name: "Delegated route agent", + role: "worker", + adapterType: "process", + adapterConfig: {}, + }).returning().then((rows) => rows[0]!); + const application = await db.insert(toolApplications).values({ + companyId: company.id, + applicationKey: `route-app-${randomUUID()}`, + name: "Route personal app", + type: "mcp", + status: "active", + }).returning().then((rows) => rows[0]!); + const connection = await db.insert(toolConnections).values({ + companyId: company.id, + applicationId: application.id, + name: "Route personal connection", + uid: `route-connection-${randomUUID()}`, + connectionKind: "managed", + ownership: "customer", + transport: "mcp_remote", + authKind: "oauth", + credentialPolicy: "per_user", + status: "active", + enabled: true, + }).returning().then((rows) => rows[0]!); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: member.principalId, + status: "active", + }).returning().then((rows) => rows[0]!); + await db.insert(connectionGrantDelegations).values({ + companyId: company.id, + grantId: grant.id, + agentId: agent.id, + createdByUserId: member.principalId, + }); + + const res = await request(await createApp(db, company.id, owner.principalId)) + .patch(`/api/companies/${company.id}/members/${member.id}`) + .send({ status: "suspended" }); + + expect(res.status, JSON.stringify(res.body)).toBe(200); + expect(res.body.status).toBe("suspended"); + expect(await db.select().from(connectionGrantDelegations)).toHaveLength(0); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, grant.id))) + .toEqual([expect.objectContaining({ status: "revoked" })]); + + await request(await createApp(db, company.id, owner.principalId)) + .patch(`/api/companies/${company.id}/members/${member.id}/role-and-grants`) + .send({ status: "active", grants: [] }) + .expect(200); + await db.update(connectionGrants).set({ status: "active" }).where(eq(connectionGrants.id, grant.id)); + await db.insert(connectionGrantDelegations).values({ + companyId: company.id, + grantId: grant.id, + agentId: agent.id, + createdByUserId: member.principalId, + }); + + const permissionsRoute = await request(await createApp(db, company.id, owner.principalId)) + .patch(`/api/companies/${company.id}/members/${member.id}/role-and-grants`) + .send({ status: "suspended", grants: [] }); + expect(permissionsRoute.status, JSON.stringify(permissionsRoute.body)).toBe(200); + expect(await db.select().from(connectionGrantDelegations)).toHaveLength(0); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, grant.id))) + .toEqual([expect.objectContaining({ status: "revoked" })]); + }); }); diff --git a/server/src/__tests__/access-service.test.ts b/server/src/__tests__/access-service.test.ts index a0daac807a..3482a5784b 100644 --- a/server/src/__tests__/access-service.test.ts +++ b/server/src/__tests__/access-service.test.ts @@ -1,14 +1,24 @@ import { randomUUID } from "node:crypto"; -import { and, eq, sql } from "drizzle-orm"; +import { and, eq, inArray, sql } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { agents, + companySecretBindings, + companySecrets, companies, companyMemberships, createDb, + connectionGrantDelegations, + connectionGrantMembers, + connectionGrants, instanceUserRoles, issues, principalPermissionGrants, + toolAccessAuditEvents, + toolApplications, + toolConnections, + userSecretDeclarations, + userSecretDefinitions, } from "@paperclipai/db"; import { getEmbeddedPostgresTestSupport, @@ -56,6 +66,16 @@ describeEmbeddedPostgres("access service", () => { }, 20_000); afterEach(async () => { + await db.delete(toolAccessAuditEvents); + await db.delete(userSecretDeclarations); + await db.delete(companySecretBindings); + await db.delete(connectionGrantDelegations); + await db.delete(connectionGrantMembers); + await db.delete(connectionGrants); + await db.delete(toolConnections); + await db.delete(toolApplications); + await db.delete(companySecrets); + await db.delete(userSecretDefinitions); await db.delete(issues); await db.delete(principalPermissionGrants); await db.delete(instanceUserRoles); @@ -226,6 +246,639 @@ describeEmbeddedPostgres("access service", () => { ).rejects.toThrow("Instance admins cannot be removed from company access"); }); + it("sweeps personal grants when instance-level company access is removed", async () => { + const { company, owner } = await createCompanyWithOwner(db); + const member = await db.insert(companyMemberships).values({ + companyId: company.id, + principalType: "user", + principalId: `member-${randomUUID()}`, + status: "active", + membershipRole: "member", + }).returning().then((rows) => rows[0]!); + const agent = await db.insert(agents).values({ + companyId: company.id, + name: "Instance access delegated agent", + role: "worker", + adapterType: "process", + adapterConfig: {}, + }).returning().then((rows) => rows[0]!); + const application = await db.insert(toolApplications).values({ + companyId: company.id, + applicationKey: `instance-access-${randomUUID()}`, + name: "Instance access app", + type: "mcp", + status: "active", + }).returning().then((rows) => rows[0]!); + const connection = await db.insert(toolConnections).values({ + companyId: company.id, + applicationId: application.id, + name: "Instance access connection", + uid: `instance-access-${randomUUID()}`, + connectionKind: "managed", + ownership: "customer", + transport: "mcp_remote", + authKind: "oauth", + credentialPolicy: "per_user", + status: "active", + enabled: true, + }).returning().then((rows) => rows[0]!); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: member.principalId, + status: "active", + }).returning().then((rows) => rows[0]!); + await db.insert(connectionGrantDelegations).values({ + companyId: company.id, + grantId: grant.id, + agentId: agent.id, + createdByUserId: member.principalId, + }); + + await accessService(db).setUserCompanyAccess(member.principalId, [], { + actorUserId: owner.principalId, + }); + + expect(await db.select().from(companyMemberships).where(eq(companyMemberships.id, member.id))) + .toEqual([expect.objectContaining({ status: "archived" })]); + expect(await db.select().from(connectionGrantDelegations)).toHaveLength(0); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, grant.id))) + .toEqual([expect.objectContaining({ status: "revoked" })]); + }); + + it("revokes personal connection access and destroys user-scoped secrets when membership is archived", async () => { + const { company, owner } = await createCompanyWithOwner(db); + const member = await db.insert(companyMemberships).values({ + companyId: company.id, + principalType: "user", + principalId: `member-${randomUUID()}`, + status: "active", + membershipRole: "member", + }).returning().then((rows) => rows[0]!); + const agent = await db.insert(agents).values({ + companyId: company.id, + name: "Delegated agent", + role: "worker", + adapterType: "process", + adapterConfig: {}, + }).returning().then((rows) => rows[0]!); + const application = await db.insert(toolApplications).values({ + companyId: company.id, + applicationKey: `app-${randomUUID()}`, + name: "Personal mail", + type: "mcp", + status: "active", + }).returning().then((rows) => rows[0]!); + const connection = await db.insert(toolConnections).values({ + companyId: company.id, + applicationId: application.id, + name: "Mail", + uid: `mail-${randomUUID()}`, + connectionKind: "managed", + ownership: "customer", + transport: "mcp_remote", + authKind: "oauth", + credentialPolicy: "per_user", + status: "active", + enabled: true, + }).returning().then((rows) => rows[0]!); + const definition = await db.insert(userSecretDefinitions).values({ + companyId: company.id, + key: `oauth-${randomUUID()}`, + name: "Personal OAuth token", + }).returning().then((rows) => rows[0]!); + const secret = await db.insert(companySecrets).values({ + companyId: company.id, + scope: "user", + ownerUserId: member.principalId, + userSecretDefinitionId: definition.id, + key: `oauth-${randomUUID()}`, + name: `OAuth ${randomUUID()}`, + }).returning().then((rows) => rows[0]!); + await db.update(toolConnections).set({ + credentialSecretRefs: [{ secretId: secret.id, configPath: "oauth.access_token" }], + }).where(eq(toolConnections.id, connection.id)); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: member.principalId, + credentialSecretRefs: [{ secretId: secret.id, configPath: "oauth.access_token" }], + }).returning().then((rows) => rows[0]!); + await db.insert(connectionGrantDelegations).values({ + companyId: company.id, + grantId: grant.id, + agentId: agent.id, + createdByUserId: member.principalId, + }); + await accessService(db).archiveMember(company.id, member.id, { + reassignment: { assigneeUserId: owner.principalId }, + }); + + expect(await db.select().from(connectionGrantDelegations)).toHaveLength(0); + expect(await db.select().from(connectionGrantMembers)).toHaveLength(0); + expect(await db.select().from(companySecrets).where(eq(companySecrets.id, secret.id))).toHaveLength(0); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, grant.id))) + .toEqual([expect.objectContaining({ status: "revoked", credentialSecretRefs: [] })]); + expect(await db.select().from(toolConnections).where(eq(toolConnections.id, connection.id))) + .toEqual([expect.objectContaining({ + status: "draft", + enabled: false, + healthStatus: "missing_secret", + lastError: "oauth_reauthorization_required", + credentialSecretRefs: [], + })]); + expect(await db.select().from(toolAccessAuditEvents).where(eq(toolAccessAuditEvents.reasonCode, "membership_removed"))) + .toHaveLength(1); + }); + + it("retains a personal credential while another member grant still uses it", async () => { + const { company, owner } = await createCompanyWithOwner(db); + const [departing, surviving] = await db.insert(companyMemberships).values([ + { + companyId: company.id, + principalType: "user" as const, + principalId: `departing-${randomUUID()}`, + status: "active" as const, + membershipRole: "member" as const, + }, + { + companyId: company.id, + principalType: "user" as const, + principalId: `surviving-${randomUUID()}`, + status: "active" as const, + membershipRole: "member" as const, + }, + ]).returning(); + const application = await db.insert(toolApplications).values({ + companyId: company.id, + applicationKey: `shared-personal-${randomUUID()}`, + name: "Shared personal app", + type: "mcp", + status: "active", + }).returning().then((rows) => rows[0]!); + const connection = await db.insert(toolConnections).values({ + companyId: company.id, + applicationId: application.id, + name: "Shared personal connection", + uid: `shared-personal-${randomUUID()}`, + connectionKind: "managed", + ownership: "customer", + transport: "mcp_remote", + authKind: "oauth", + credentialPolicy: "per_user", + status: "active", + enabled: true, + }).returning().then((rows) => rows[0]!); + const definition = await db.insert(userSecretDefinitions).values({ + companyId: company.id, + key: `shared-personal-${randomUUID()}`, + name: "Shared personal token", + }).returning().then((rows) => rows[0]!); + const secret = await db.insert(companySecrets).values({ + companyId: company.id, + scope: "user", + ownerUserId: departing!.principalId, + userSecretDefinitionId: definition.id, + key: `shared-personal-${randomUUID()}`, + name: "Shared personal token", + }).returning().then((rows) => rows[0]!); + const credentialSecretRefs = [{ secretId: secret.id, configPath: "oauth.access_token" }]; + await db.update(toolConnections).set({ credentialSecretRefs }).where(eq(toolConnections.id, connection.id)); + const [departingGrant, survivingGrant] = await db.insert(connectionGrants).values([ + { + companyId: company.id, + connectionId: connection.id, + kind: "user" as const, + subjectUserId: departing!.principalId, + status: "active" as const, + credentialSecretRefs, + }, + { + companyId: company.id, + connectionId: connection.id, + kind: "user" as const, + subjectUserId: surviving!.principalId, + status: "active" as const, + credentialSecretRefs, + }, + ]).returning(); + + await accessService(db).archiveMember(company.id, departing!.id, { + reassignment: { assigneeUserId: owner.principalId }, + }); + + expect(await db.select().from(companySecrets).where(eq(companySecrets.id, secret.id))) + .toHaveLength(1); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, departingGrant!.id))) + .toEqual([expect.objectContaining({ status: "revoked", credentialSecretRefs: [] })]); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, survivingGrant!.id))) + .toEqual([expect.objectContaining({ status: "active", credentialSecretRefs })]); + expect(await db.select().from(toolConnections).where(eq(toolConnections.id, connection.id))) + .toEqual([expect.objectContaining({ status: "active", enabled: true, credentialSecretRefs })]); + }); + + it("keeps a mixed connection active when an unaffected organization credential survives", async () => { + const { company, owner } = await createCompanyWithOwner(db); + const departing = await db.insert(companyMemberships).values({ + companyId: company.id, + principalType: "user", + principalId: `departing-mixed-${randomUUID()}`, + status: "active", + membershipRole: "member", + }).returning().then((rows) => rows[0]!); + const application = await db.insert(toolApplications).values({ + companyId: company.id, + applicationKey: `mixed-${randomUUID()}`, + name: "Mixed credential app", + type: "mcp", + status: "active", + }).returning().then((rows) => rows[0]!); + const definition = await db.insert(userSecretDefinitions).values({ + companyId: company.id, + key: `mixed-personal-${randomUUID()}`, + name: "Mixed personal token", + }).returning().then((rows) => rows[0]!); + const personalSecret = await db.insert(companySecrets).values({ + companyId: company.id, + scope: "user", + ownerUserId: departing.principalId, + userSecretDefinitionId: definition.id, + key: `mixed-personal-${randomUUID()}`, + name: "Mixed personal token", + }).returning().then((rows) => rows[0]!); + const organizationSecret = await db.insert(companySecrets).values({ + companyId: company.id, + scope: "company", + key: `mixed-organization-${randomUUID()}`, + name: "Mixed organization token", + }).returning().then((rows) => rows[0]!); + const personalRef = { secretId: personalSecret.id, configPath: "credentials.personal" }; + const organizationRef = { secretId: organizationSecret.id, configPath: "credentials.organization" }; + const connection = await db.insert(toolConnections).values({ + companyId: company.id, + applicationId: application.id, + name: "Mixed credential connection", + uid: `mixed-${randomUUID()}`, + connectionKind: "managed", + ownership: "customer", + transport: "mcp_remote", + authKind: "api_key", + credentialPolicy: "per_user_with_fallback", + status: "active", + enabled: true, + healthStatus: "healthy", + credentialSecretRefs: [personalRef, organizationRef], + }).returning().then((rows) => rows[0]!); + const personalGrant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: departing.principalId, + credentialSecretRefs: [personalRef], + }).returning().then((rows) => rows[0]!); + const organizationGrant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "organization", + status: "active", + isDefault: true, + credentialSecretRefs: [organizationRef], + }).returning().then((rows) => rows[0]!); + + await accessService(db).archiveMember(company.id, departing.id, { + reassignment: { assigneeUserId: owner.principalId }, + }); + + expect(await db.select().from(companySecrets).where(eq(companySecrets.id, personalSecret.id))) + .toHaveLength(0); + expect(await db.select().from(companySecrets).where(eq(companySecrets.id, organizationSecret.id))) + .toHaveLength(1); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, personalGrant.id))) + .toEqual([expect.objectContaining({ status: "revoked", credentialSecretRefs: [] })]); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, organizationGrant.id))) + .toEqual([expect.objectContaining({ status: "active", credentialSecretRefs: [organizationRef] })]); + expect(await db.select().from(toolConnections).where(eq(toolConnections.id, connection.id))) + .toEqual([expect.objectContaining({ + status: "active", + enabled: true, + healthStatus: "healthy", + lastError: null, + credentialSecretRefs: [organizationRef], + })]); + }); + + it("keeps a sole organization audience dormant until its member is reactivated", async () => { + const { company, owner } = await createCompanyWithOwner(db); + const departing = await db.insert(companyMemberships).values({ + companyId: company.id, + principalType: "user", + principalId: `departing-org-${randomUUID()}`, + status: "active", + membershipRole: "member", + }).returning().then((rows) => rows[0]!); + const application = await db.insert(toolApplications).values({ + companyId: company.id, + applicationKey: `shared-org-${randomUUID()}`, + name: "Shared organization app", + type: "mcp", + status: "active", + }).returning().then((rows) => rows[0]!); + const connection = await db.insert(toolConnections).values({ + companyId: company.id, + applicationId: application.id, + name: "Shared organization connection", + uid: `shared-org-${randomUUID()}`, + connectionKind: "managed", + ownership: "customer", + transport: "mcp_remote", + authKind: "oauth", + credentialPolicy: "shared", + status: "active", + enabled: true, + }).returning().then((rows) => rows[0]!); + const definition = await db.insert(userSecretDefinitions).values({ + companyId: company.id, + key: `shared-org-${randomUUID()}`, + name: "Shared organization token", + }).returning().then((rows) => rows[0]!); + const secret = await db.insert(companySecrets).values({ + companyId: company.id, + scope: "user", + ownerUserId: departing.principalId, + userSecretDefinitionId: definition.id, + key: `shared-org-${randomUUID()}`, + name: "Shared organization token", + }).returning().then((rows) => rows[0]!); + const credentialSecretRefs = [{ secretId: secret.id, configPath: "oauth.access_token" }]; + await db.update(toolConnections).set({ credentialSecretRefs }).where(eq(toolConnections.id, connection.id)); + await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: departing.principalId, + status: "active", + credentialSecretRefs, + }); + const organizationGrant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "organization", + status: "active", + isDefault: true, + credentialSecretRefs, + }).returning().then((rows) => rows[0]!); + await db.insert(connectionGrantMembers).values({ + companyId: company.id, + grantId: organizationGrant.id, + subjectType: "user", + subjectId: departing.principalId, + }); + + await accessService(db).archiveMember(company.id, departing.id, { + reassignment: { assigneeUserId: owner.principalId }, + }); + + expect(await db.select().from(companySecrets).where(eq(companySecrets.id, secret.id))) + .toHaveLength(1); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, organizationGrant.id))) + .toEqual([expect.objectContaining({ status: "active", isDefault: true, credentialSecretRefs })]); + expect(await db.select().from(connectionGrantMembers).where(eq(connectionGrantMembers.grantId, organizationGrant.id))) + .toEqual([expect.objectContaining({ subjectId: departing.principalId })]); + expect(await db.select().from(toolConnections).where(eq(toolConnections.id, connection.id))) + .toEqual([expect.objectContaining({ status: "active", enabled: true, credentialSecretRefs })]); + + await accessService(db).setUserCompanyAccess(departing.principalId, [company.id]); + + expect(await db.select().from(companySecrets).where(eq(companySecrets.id, secret.id))) + .toHaveLength(1); + expect(await db.select().from(companyMemberships).where(eq(companyMemberships.id, departing.id))) + .toEqual([expect.objectContaining({ status: "active" })]); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, organizationGrant.id))) + .toEqual([expect.objectContaining({ status: "active", credentialSecretRefs })]); + }); + + it("revokes delegated personal connection access when membership is suspended", async () => { + const { company } = await createCompanyWithOwner(db); + const member = await db.insert(companyMemberships).values({ + companyId: company.id, + principalType: "user", + principalId: `member-${randomUUID()}`, + status: "active", + membershipRole: "member", + }).returning().then((rows) => rows[0]!); + const agent = await db.insert(agents).values({ + companyId: company.id, + name: "Delegated agent", + role: "worker", + adapterType: "process", + adapterConfig: {}, + }).returning().then((rows) => rows[0]!); + const application = await db.insert(toolApplications).values({ + companyId: company.id, + applicationKey: `app-${randomUUID()}`, + name: "Personal mail", + type: "mcp", + status: "active", + }).returning().then((rows) => rows[0]!); + const connection = await db.insert(toolConnections).values({ + companyId: company.id, + applicationId: application.id, + name: "Mail", + uid: `mail-${randomUUID()}`, + connectionKind: "managed", + ownership: "customer", + transport: "mcp_remote", + authKind: "oauth", + credentialPolicy: "per_user", + }).returning().then((rows) => rows[0]!); + const definition = await db.insert(userSecretDefinitions).values({ + companyId: company.id, + key: `oauth-${randomUUID()}`, + name: "Personal OAuth token", + }).returning().then((rows) => rows[0]!); + const secret = await db.insert(companySecrets).values({ + companyId: company.id, + scope: "user", + ownerUserId: member.principalId, + userSecretDefinitionId: definition.id, + key: `oauth-${randomUUID()}`, + name: `OAuth ${randomUUID()}`, + }).returning().then((rows) => rows[0]!); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: member.principalId, + credentialSecretRefs: [{ secretId: secret.id, configPath: "oauth.access_token" }], + }).returning().then((rows) => rows[0]!); + await db.insert(connectionGrantDelegations).values({ + companyId: company.id, + grantId: grant.id, + agentId: agent.id, + createdByUserId: member.principalId, + }); + + await accessService(db).updateMember(company.id, member.id, { status: "suspended" }); + + expect(await db.select().from(connectionGrantDelegations)).toHaveLength(0); + expect(await db.select().from(companySecrets).where(eq(companySecrets.id, secret.id))).toHaveLength(0); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, grant.id))) + .toEqual([expect.objectContaining({ status: "revoked", credentialSecretRefs: [] })]); + }); + + it("preserves personal grant secrets used by surviving declarations and bindings", async () => { + const { company } = await createCompanyWithOwner(db); + const member = await db.insert(companyMemberships).values({ + companyId: company.id, + principalType: "user", + principalId: `member-${randomUUID()}`, + status: "active", + membershipRole: "member", + }).returning().then((rows) => rows[0]!); + const application = await db.insert(toolApplications).values({ + companyId: company.id, + applicationKey: `app-${randomUUID()}`, + name: "Personal shared credentials", + type: "mcp", + status: "active", + }).returning().then((rows) => rows[0]!); + const definitions = await db.insert(userSecretDefinitions).values([ + { companyId: company.id, key: `agent-declared-${randomUUID()}`, name: "Agent-declared credential" }, + { companyId: company.id, key: `environment-declared-${randomUUID()}`, name: "Environment-declared credential" }, + { companyId: company.id, key: `bound-${randomUUID()}`, name: "Bound credential" }, + ]).returning(); + const secrets = await db.insert(companySecrets).values(definitions.map((definition, index) => ({ + companyId: company.id, + scope: "user", + ownerUserId: member.principalId, + userSecretDefinitionId: definition.id, + key: `shared-${index}-${randomUUID()}`, + name: `Shared credential ${index}`, + }))).returning(); + const connection = await db.insert(toolConnections).values({ + companyId: company.id, + applicationId: application.id, + name: "Shared personal connection", + uid: `shared-${randomUUID()}`, + connectionKind: "managed", + ownership: "customer", + transport: "mcp_remote", + authKind: "api_key", + credentialPolicy: "per_user", + status: "active", + enabled: true, + credentialSecretRefs: secrets.map((secret, index) => ({ + secretId: secret.id, + configPath: `credentials.shared_${index}`, + })), + }).returning().then((rows) => rows[0]!); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: member.principalId, + credentialSecretRefs: secrets.map((secret, index) => ({ + secretId: secret.id, + configPath: `credentials.shared_${index}`, + })), + }).returning().then((rows) => rows[0]!); + await db.insert(userSecretDeclarations).values([ + { + companyId: company.id, + userSecretDefinitionId: definitions[0]!.id, + targetType: "agent", + targetId: `surviving-agent-${randomUUID()}`, + configPath: "env.AGENT_TOKEN", + envKey: "AGENT_TOKEN", + }, + { + companyId: company.id, + userSecretDefinitionId: definitions[1]!.id, + targetType: "environment", + targetId: `surviving-environment-${randomUUID()}`, + configPath: "env.ENVIRONMENT_TOKEN", + envKey: "ENVIRONMENT_TOKEN", + }, + ]); + await db.insert(companySecretBindings).values([ + ...secrets.map((secret, index) => ({ + companyId: company.id, + secretId: secret.id, + targetType: "tool_connection", + targetId: connection.id, + configPath: `credentials.shared_${index}`, + })), + { + companyId: company.id, + secretId: secrets[2]!.id, + targetType: "environment", + targetId: `surviving-environment-${randomUUID()}`, + configPath: "env.BOUND_TOKEN", + }, + ]); + + await accessService(db).updateMember(company.id, member.id, { status: "suspended" }); + + expect(await db.select().from(companySecrets).where(inArray(companySecrets.id, secrets.map((secret) => secret.id)))) + .toHaveLength(3); + expect(await db.select().from(connectionGrants).where(eq(connectionGrants.id, grant.id))) + .toEqual([expect.objectContaining({ status: "revoked", credentialSecretRefs: [] })]); + expect(await db.select().from(toolConnections).where(eq(toolConnections.id, connection.id))) + .toEqual([expect.objectContaining({ + status: "draft", + enabled: false, + credentialSecretRefs: [], + })]); + expect(await db.select().from(companySecretBindings)).toEqual([ + expect.objectContaining({ + secretId: secrets[2]!.id, + targetType: "environment", + }), + ]); + expect(await db.select().from(userSecretDeclarations)).toHaveLength(2); + }); + + it("preserves unrelated user-scoped secrets when membership is suspended", async () => { + const { company } = await createCompanyWithOwner(db); + const member = await db.insert(companyMemberships).values({ + companyId: company.id, + principalType: "user", + principalId: `member-${randomUUID()}`, + status: "active", + membershipRole: "member", + }).returning().then((rows) => rows[0]!); + const definition = await db.insert(userSecretDefinitions).values({ + companyId: company.id, + key: `oauth-${randomUUID()}`, + name: "Personal OAuth token", + }).returning().then((rows) => rows[0]!); + const unrelatedSecret = await db.insert(companySecrets).values({ + companyId: company.id, + scope: "user", + ownerUserId: member.principalId, + userSecretDefinitionId: definition.id, + key: `oauth-${randomUUID()}`, + name: "Environment API key", + }).returning().then((rows) => rows[0]!); + const otherUserSecret = await db.insert(companySecrets).values({ + companyId: company.id, + scope: "user", + ownerUserId: "another-user", + userSecretDefinitionId: definition.id, + key: `oauth-${randomUUID()}`, + name: "Another user's credential", + }).returning().then((rows) => rows[0]!); + + await accessService(db).updateMember(company.id, member.id, { status: "suspended" }); + + expect(await db.select().from(companySecrets).where(eq(companySecrets.id, unrelatedSecret.id))) + .toHaveLength(1); + expect(await db.select().from(companySecrets).where(eq(companySecrets.id, otherUserSecret.id))) + .toHaveLength(1); + }); + it("allows owner and admin role-default grants to manage environments", async () => { const { company, owner } = await createCompanyWithOwner(db); const access = accessService(db); diff --git a/server/src/__tests__/attention-service.test.ts b/server/src/__tests__/attention-service.test.ts index e4d2f3bf64..49f1e17466 100644 --- a/server/src/__tests__/attention-service.test.ts +++ b/server/src/__tests__/attention-service.test.ts @@ -860,9 +860,23 @@ describeEmbeddedPostgres("attention service", () => { addresseeAgentId: reviewerId, payload: { version: 1, questions: [] }, }); + await db.insert(issueThreadInteractions).values({ + id: randomUUID(), + companyId, + issueId, + kind: "ask_user_questions", + status: "pending", + title: "User-addressed question", + createdByAgentId: workerId, + addresseeUserId: "board-user", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + payload: { version: 1, questions: [] }, + }); await agentService(db).pause(reviewerId); const feed = await attentionService(db).list(companyId, { userId: "board-user" }); + const otherUserFeed = await attentionService(db).list(companyId, { userId: "other-user" }); const audienceByTitle = new Map(feed.items .filter((item) => item.sourceKind === "issue_thread_interaction") .map((item) => [item.subject.title, item.resolverAudience])); @@ -888,6 +902,11 @@ describeEmbeddedPostgres("attention service", () => { addresseeAgentId: reviewerId, addresseeName: "Reviewer", }); + expect(audienceByTitle.get("User-addressed question")).toMatchObject({ + addresseeUserId: "board-user", + effectiveResolverPolicy: "human_only", + }); + expect(otherUserFeed.items.some((item) => item.subject.title === "User-addressed question")).toBe(false); // Non-interaction rows carry no resolver policy at all. expect(feed.items.find((item) => item.sourceKind !== "issue_thread_interaction")?.resolverAudience) .toBeNull(); diff --git a/server/src/__tests__/connection-grant-resolution.test.ts b/server/src/__tests__/connection-grant-resolution.test.ts new file mode 100644 index 0000000000..b848704e2f --- /dev/null +++ b/server/src/__tests__/connection-grant-resolution.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vitest"; +import { + isConnectionGrantAudienceAllowed, + resolveCredentialGrantKind, +} from "../services/tool-gateway.js"; + +describe("connection grant resolution", () => { + it("resolves every credential policy deterministically", () => { + expect(resolveCredentialGrantKind("shared", "alice", true)).toBe("organization"); + expect(resolveCredentialGrantKind("per_user", "alice", true)).toBe("user"); + expect(resolveCredentialGrantKind("per_user", "alice", false)).toBe("user_authorization_required"); + expect(resolveCredentialGrantKind("per_user", null, false)).toBe("user_authorization_required"); + expect(resolveCredentialGrantKind("per_user_with_fallback", "alice", true)).toBe("user"); + expect(resolveCredentialGrantKind("per_user_with_fallback", "alice", false)).toBe("organization"); + expect(resolveCredentialGrantKind("per_user_with_fallback", null, false)).toBe("organization"); + }); + + it("allows an empty audience and rejects users outside a restricted audience", () => { + expect(isConnectionGrantAudienceAllowed([], "alice", true)).toBe(true); + expect(isConnectionGrantAudienceAllowed(["alice", "bob"], "alice", true)).toBe(true); + expect(isConnectionGrantAudienceAllowed(["alice", "bob"], "carol", true)).toBe(false); + expect(isConnectionGrantAudienceAllowed(["alice"], null, false)).toBe(false); + expect(isConnectionGrantAudienceAllowed([], "alice", false)).toBe(false); + expect(isConnectionGrantAudienceAllowed(["alice"], "alice", false)).toBe(false); + }); +}); diff --git a/server/src/__tests__/generic-mcp-connection.test.ts b/server/src/__tests__/generic-mcp-connection.test.ts index 664e9ff127..bde32bf2f6 100644 --- a/server/src/__tests__/generic-mcp-connection.test.ts +++ b/server/src/__tests__/generic-mcp-connection.test.ts @@ -366,6 +366,9 @@ describeEmbeddedPostgres("generic remote MCP connections", () => { // this connection cannot be depending on gallery metadata for anything. expect(connection!.config).not.toHaveProperty("sourceTemplateKey"); expect(connection!.config).not.toHaveProperty("connectionMethodKey"); + await expect(service.listConnectionGrants(result.connectionId, company.id)).resolves.toMatchObject({ + grants: [expect.objectContaining({ kind: "organization", isDefault: true, credentialSecretRefs: [] })], + }); const profiles = await db.select().from(toolProfiles).where(eq( toolProfiles.profileKey, `app:${result.connectionId}`, @@ -902,6 +905,7 @@ describeEmbeddedPostgres("generic remote MCP connections", () => { }); it("redacts a hostile denial from the callback route and consumes the state", async () => { + vi.stubEnv("PAPERCLIP_PUBLIC_URL", PUBLIC_BASE_URL); installMcpOAuthFixture({ auth: "oauth" }); const company = await createCompany(db); const service = toolAccessService(db); @@ -966,6 +970,7 @@ describeEmbeddedPostgres("generic remote MCP connections", () => { }); it("returns browser denials to setup without reflecting provider-authored details", async () => { + vi.stubEnv("PAPERCLIP_PUBLIC_URL", PUBLIC_BASE_URL); installMcpOAuthFixture({ auth: "oauth" }); const company = await createCompany(db); const service = toolAccessService(db); diff --git a/server/src/__tests__/plugin-worker-manager.test.ts b/server/src/__tests__/plugin-worker-manager.test.ts index 4aac14fab4..56c268dc7a 100644 --- a/server/src/__tests__/plugin-worker-manager.test.ts +++ b/server/src/__tests__/plugin-worker-manager.test.ts @@ -1218,7 +1218,7 @@ describe("plugin worker manager setup-token pty route gate", () => { } finally { await handle.stop().catch(() => undefined); } - }); + }, 15_000); it("routes delayed input to the worker and back to the listener", async () => { const handle = makeLoginPtyHandle(); diff --git a/server/src/__tests__/smoke-lab.test.ts b/server/src/__tests__/smoke-lab.test.ts index 3a9eb4c258..297f5843ad 100644 --- a/server/src/__tests__/smoke-lab.test.ts +++ b/server/src/__tests__/smoke-lab.test.ts @@ -6,6 +6,7 @@ import { activityLog, agents, authUsers, + connectionGrants, companies, companyMemberships, createDb, @@ -354,10 +355,13 @@ describeEmbeddedPostgres("smoke lab service pack and results API", () => { const applications = await db.select().from(toolApplications).where(eq(toolApplications.companyId, company.id)); const connections = await db.select().from(toolConnections).where(eq(toolConnections.companyId, company.id)); + const grants = await db.select().from(connectionGrants).where(eq(connectionGrants.companyId, company.id)); const catalog = await db.select().from(toolCatalogEntries).where(eq(toolCatalogEntries.companyId, company.id)); const profiles = await db.select().from(toolProfiles).where(eq(toolProfiles.companyId, company.id)); expect(applications).toHaveLength(2); expect(connections).toHaveLength(2); + expect(grants).toHaveLength(2); + expect(grants.every((grant) => grant.kind === "organization" && grant.isDefault)).toBe(true); expect(catalog.some((entry) => entry.toolName === "todo.add" && entry.riskLevel === "write")).toBe(true); expect(catalog.some((entry) => entry.toolName === "time.now" && entry.riskLevel === "read")).toBe(true); expect(profiles).toHaveLength(1); diff --git a/server/src/__tests__/tool-access-policy-service.test.ts b/server/src/__tests__/tool-access-policy-service.test.ts index 1f6e683bec..b573c4b51d 100644 --- a/server/src/__tests__/tool-access-policy-service.test.ts +++ b/server/src/__tests__/tool-access-policy-service.test.ts @@ -1660,7 +1660,7 @@ describeEmbeddedPostgres("tool access policy service", () => { await expect(svc.createConnection(company.id, { name: "Wrong secret", transport: "mcp_remote", - transportConfig: { url: "https://example.invalid/mcp" }, + transportConfig: { url: "https://8.8.8.8/mcp" }, credentialSecretRefs: [{ secretId: otherSecret.id, configPath: "headers.Authorization", diff --git a/server/src/__tests__/tool-access-service.test.ts b/server/src/__tests__/tool-access-service.test.ts index bd62ba102a..1408db670a 100644 --- a/server/src/__tests__/tool-access-service.test.ts +++ b/server/src/__tests__/tool-access-service.test.ts @@ -9,6 +9,8 @@ import { companies, companyMemberships, companySecretBindings, + connectionGrantMembers, + connectionGrantDelegations, connectionGrants, connectionTokenIssuances, companySecrets, @@ -36,13 +38,14 @@ import { toolRuntimeSlots, toolStdioCommandTemplates, } from "@paperclipai/db"; -import { and, eq } from "drizzle-orm"; +import { and, eq, inArray, sql } from "drizzle-orm"; import { getConnectableAppDefinition } from "@paperclipai/shared"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase, } from "./helpers/embedded-postgres.js"; -import { classifyRisk, normalizeConnectionMethodConfig, toolAccessService as toolAccessServiceBase } from "../services/tool-access.js"; +import { classifyRisk, normalizeConnectionMethodConfig, toolAccessService } from "../services/tool-access.js"; +import { accessService } from "../services/access.js"; import { toolAccessPolicyService } from "../services/tool-access-policy.js"; import { secretService } from "../services/secrets.js"; import { canonicalToolArguments, signToolArguments } from "../services/tool-content-guards.js"; @@ -58,11 +61,11 @@ const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : * remote servers with global fetch fixtures. Keep those protocol fixtures * deterministic while the dedicated rebinding suite exercises real pinning. */ -function toolAccessService( +function createTestToolAccessService( db: ReturnType, - options: Parameters[1] = {}, + options: Parameters[1] = {}, ) { - return toolAccessServiceBase(db, { + return toolAccessService(db, { remoteHttpEndpointLookup: async () => [{ address: "8.8.8.8", family: 4 }], remoteHttpRequest: async (url, init) => fetch(url, init), ...options, @@ -238,6 +241,13 @@ async function createAgent(db: ReturnType, companyId: string, s } async function createIssueAndRun(db: ReturnType, companyId: string, agentId: string) { + await db.insert(companyMemberships).values({ + companyId, + principalType: "user", + principalId: "user-for-run", + status: "active", + membershipRole: "member", + }).onConflictDoNothing(); const [issue] = await db.insert(issues).values({ companyId, title: `Broker issue ${randomUUID()}`, @@ -455,7 +465,16 @@ async function createRemoteToolFixture( config: { url: "https://fixture.example.test/mcp" }, transportConfig: { url: "https://fixture.example.test/mcp" }, healthStatus: "ok", + credentialPolicy: "shared", }).returning(); + await db.insert(connectionGrants).values({ + companyId, + connectionId: connection!.id, + kind: "organization", + credentialSecretRefs: [], + status: "active", + isDefault: true, + }); const riskLevel = input.riskLevel ?? "write"; const [catalogEntry] = await db.insert(toolCatalogEntries).values({ companyId, @@ -836,26 +855,453 @@ describeEmbeddedPostgres("tool access service", () => { subject: { type: "user", userId: "someone-else" }, }); + await db.update(toolConnections).set({ credentialPolicy: "per_user" }).where(eq(toolConnections.id, connection.id)); const missing = await request(app) .post(`/api/agents/me/connections/${encodeURIComponent(connection.uid)}/token`) .send({ subject: { type: "user", userId: "user-for-run" } }); expect(missing.status).toBe(409); expect(missing.body).toMatchObject({ code: "user_authorization_required", remediation: { action: "start_authorization" } }); - const service = toolAccessService(db); - const grant = await service.addConnectionInstallation(connection.id, { isDefault: false }); - await service.revokeConnectionGrant(connection.id, grant.id); + const [grant] = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: "user-for-run", + status: "revoked", + isDefault: false, + }).returning(); const revoked = await request(app) .post(`/api/agents/me/connections/${connection.id}/token`) - .send({ grantId: grant.id }); + .send({}); expect(revoked.status).toBe(409); expect(revoked.body).toMatchObject({ code: "grant_revoked", grantId: grant.id }); }); + it("allows only the personal grant owner to create named-agent delegations and audits revocation", async () => { + const company = await createCompany(db); + const agent = await createAgent(db, company.id); + await grantBoardUser(db, company.id, "alice", [], "member"); + await grantBoardUser(db, company.id, "mallory", [], "member"); + const { connection } = await createBrokerConnection(db, company.id); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: "alice", + status: "active", + isDefault: false, + }).returning().then((rows) => rows[0]!); + const service = createTestToolAccessService(db); + + await expect(service.createConnectionGrantDelegation(connection.id, grant.id, agent.id, "mallory")) + .rejects.toThrow("Only the active personal grant owner can create a delegation"); + const delegation = await service.createConnectionGrantDelegation(connection.id, grant.id, agent.id, "alice"); + expect(delegation).toMatchObject({ grantId: grant.id, agentId: agent.id, createdByUserId: "alice" }); + + await service.revokeConnectionGrantDelegation( + connection.id, + grant.id, + delegation.id, + { actorType: "user", actorId: "manager" }, + ); + expect(await db.select().from(connectionGrantDelegations).where(eq(connectionGrantDelegations.id, delegation.id))) + .toHaveLength(0); + expect(await db.select().from(toolAccessAuditEvents).where(eq(toolAccessAuditEvents.connectionId, connection.id))) + .toEqual(expect.arrayContaining([ + expect.objectContaining({ action: "connection_grant.delegated", actorId: "alice" }), + expect.objectContaining({ action: "connection_grant.delegation_revoked", actorId: "manager" }), + ])); + }); + + it("enforces delegation owner and manager permissions through the HTTP routes", async () => { + const company = await createCompany(db); + const agent = await createAgent(db, company.id); + await grantBoardUser(db, company.id, "alice", [], "member"); + await grantBoardUser(db, company.id, "mallory", [], "member"); + await grantBoardUser(db, company.id, "manager", ["tools:manage_connections"], "operator"); + const { connection } = await createBrokerConnection(db, company.id); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: "alice", + status: "active", + isDefault: false, + }).returning().then((rows) => rows[0]!); + + const nonOwner = await request(createRouteApp( + db, + boardSessionActor(company.id, "member", "mallory"), + )) + .post(`/api/tool-connections/${connection.id}/grants/${grant.id}/delegations`) + .send({ agentId: agent.id }); + expect(nonOwner.status).toBe(403); + + const created = await request(createRouteApp( + db, + boardSessionActor(company.id, "member", "alice"), + )) + .post(`/api/tool-connections/${connection.id}/grants/${grant.id}/delegations`) + .send({ agentId: agent.id }); + expect(created.status).toBe(201); + expect(created.body).toMatchObject({ grantId: grant.id, agentId: agent.id }); + + const unrelatedRevoke = await request(createRouteApp( + db, + boardSessionActor(company.id, "member", "mallory"), + )).delete( + `/api/tool-connections/${connection.id}/grants/${grant.id}/delegations/${created.body.id}`, + ); + expect(unrelatedRevoke.status).toBe(403); + + const managerRevoke = await request(createRouteApp( + db, + boardSessionActor(company.id, "operator", "manager"), + )).delete( + `/api/tool-connections/${connection.id}/grants/${grant.id}/delegations/${created.body.id}`, + ); + expect(managerRevoke.status).toBe(200); + expect(await db.select().from(connectionGrantDelegations).where(eq( + connectionGrantDelegations.id, + created.body.id, + ))).toHaveLength(0); + expect(await db.select().from(toolAccessAuditEvents).where(eq( + toolAccessAuditEvents.connectionId, + connection.id, + ))).toEqual(expect.arrayContaining([ + expect.objectContaining({ action: "connection_grant.delegated", actorId: "alice" }), + expect.objectContaining({ action: "connection_grant.delegation_revoked", actorId: "manager" }), + ])); + }); + + it("serializes delegation creation behind membership removal so reauthorization cannot revive stale consent", async () => { + const company = await createCompany(db); + const agent = await createAgent(db, company.id); + await grantBoardUser(db, company.id, "alice", [], "member"); + const { connection } = await createBrokerConnection(db, company.id); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: "alice", + status: "active", + isDefault: false, + }).returning().then((rows) => rows[0]!); + const serviceDb = createDb(tempDb!.connectionString, { maxConnections: 1 }); + const service = createTestToolAccessService(serviceDb); + const removalDb = createDb(tempDb!.connectionString, { maxConnections: 1 }); + let releaseRemoval!: () => void; + const removalMayCommit = new Promise((resolve) => { + releaseRemoval = resolve; + }); + let membershipLocked!: () => void; + const membershipIsLocked = new Promise((resolve) => { + membershipLocked = resolve; + }); + + const removal = removalDb.transaction(async (tx) => { + await tx.execute(sql` + SELECT "id" + FROM "company_memberships" + WHERE "company_id" = ${company.id} + AND "principal_type" = 'user' + AND "principal_id" = 'alice' + FOR UPDATE + `); + await tx.execute(sql` + UPDATE "connection_grants" + SET "status" = 'revoked', "revoked_at" = now(), "updated_at" = now() + WHERE "id" = ${grant.id} + `); + await tx.execute(sql` + DELETE FROM "connection_grant_delegations" + WHERE "company_id" = ${company.id} AND "grant_id" = ${grant.id} + `); + membershipLocked(); + await removalMayCommit; + await tx.execute(sql` + UPDATE "company_memberships" + SET "status" = 'suspended', "updated_at" = now() + WHERE "company_id" = ${company.id} + AND "principal_type" = 'user' + AND "principal_id" = 'alice' + `); + }); + + await membershipIsLocked; + let creationSettled = false; + const creation = service.createConnectionGrantDelegation(connection.id, grant.id, agent.id, "alice") + .then( + (value) => ({ value, error: null }), + (error: unknown) => ({ value: null, error }), + ) + .finally(() => { + creationSettled = true; + }); + await new Promise((resolve) => setTimeout(resolve, 50)); + expect(creationSettled).toBe(false); + releaseRemoval(); + await removal; + const creationResult = await creation; + expect(creationResult.error).toEqual(expect.objectContaining({ + message: "Only an active company member can delegate their personal grant", + })); + + await db.update(companyMemberships).set({ status: "active" }).where(and( + eq(companyMemberships.companyId, company.id), + eq(companyMemberships.principalId, "alice"), + )); + await db.update(connectionGrants).set({ status: "active", revokedAt: null }).where(eq(connectionGrants.id, grant.id)); + expect(await db.select().from(connectionGrantDelegations).where(eq(connectionGrantDelegations.grantId, grant.id))) + .toHaveLength(0); + }); + + it("fails autonomous token minting closed until the named agent has a standing delegation", async () => { + const company = await createCompany(db); + const agent = await createAgent(db, company.id); + const { issue, run } = await createIssueAndRun(db, company.id, agent.id); + await db.update(heartbeatRuns).set({ invocationSource: "automation" }).where(eq(heartbeatRuns.id, run.id)); + const { connection } = await createBrokerConnection(db, company.id); + await allowConnectionForAgent(db, company.id, agent.id, connection.id); + await db.update(toolConnections).set({ credentialPolicy: "per_user" }).where(eq(toolConnections.id, connection.id)); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: "user-for-run", + status: "active", + isDefault: false, + }).returning().then((rows) => rows[0]!); + const app = createRouteApp(db, agentJwtActor(company.id, agent.id, run.id)); + + const denied = await request(app) + .post(`/api/agents/me/connections/${connection.id}/token`) + .send({}); + expect(denied.status).toBe(409); + expect(denied.body).toMatchObject({ + code: "standing_delegation_required", + grantId: grant.id, + remediation: { action: "delegate_personal_grant", grantId: grant.id, agentId: agent.id }, + }); + expect(await db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.issueId, issue.id))) + .toEqual([expect.objectContaining({ + status: "pending", + addresseeUserId: "user-for-run", + idempotencyKey: `connection-delegation:${connection.id}:user-for-run:${agent.id}`, + })]); + + await db.update(companyMemberships).set({ status: "suspended" }).where(and( + eq(companyMemberships.companyId, company.id), + eq(companyMemberships.principalId, "user-for-run"), + )); + const inactiveOwner = await request(app) + .post(`/api/agents/me/connections/${connection.id}/token`) + .send({}); + expect(inactiveOwner.status).toBe(403); + expect(inactiveOwner.body).toMatchObject({ + code: "grant_owner_membership_inactive", + remediation: { action: "restore_membership_or_reconnect" }, + }); + expect(inactiveOwner.body.error).toContain("not an active company member"); + }); + + it("enforces organization grant audiences at token mint time", async () => { + const company = await createCompany(db); + const agent = await createAgent(db, company.id); + const { run } = await createIssueAndRun(db, company.id, agent.id); + const { connection } = await createBrokerConnection(db, company.id); + await allowConnectionForAgent(db, company.id, agent.id, connection.id); + const [grant] = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "organization", + credentialSecretRefs: connection.credentialSecretRefs, + status: "active", + isDefault: true, + }).returning(); + await db.insert(connectionGrantMembers).values({ + companyId: company.id, + grantId: grant!.id, + subjectType: "user", + subjectId: "user-for-run", + }); + const app = createRouteApp(db, agentJwtActor(company.id, agent.id, run.id)); + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue({ + ok: true, + status: 201, + json: async () => ({ token: "audience-token", expires_in: 600 }), + } as Response); + + const allowed = await request(app).post(`/api/agents/me/connections/${connection.id}/token`).send({}); + expect(allowed.status).toBe(200); + expect(fetchMock).toHaveBeenCalledTimes(1); + + await db.update(companyMemberships).set({ status: "suspended" }).where(and( + eq(companyMemberships.companyId, company.id), + eq(companyMemberships.principalId, "user-for-run"), + )); + const inactiveAudienceMember = await request(app).post(`/api/agents/me/connections/${connection.id}/token`).send({}); + expect(inactiveAudienceMember.status).toBe(403); + expect(inactiveAudienceMember.body).toMatchObject({ code: "grant_audience_denied", grantId: grant!.id }); + expect(fetchMock).toHaveBeenCalledTimes(1); + await db.update(companyMemberships).set({ status: "active" }).where(and( + eq(companyMemberships.companyId, company.id), + eq(companyMemberships.principalId, "user-for-run"), + )); + + await db.delete(connectionGrantMembers).where(eq(connectionGrantMembers.grantId, grant!.id)); + await db.insert(connectionGrantMembers).values({ + companyId: company.id, + grantId: grant!.id, + subjectType: "user", + subjectId: "sales-user", + }); + const denied = await request(app).post(`/api/agents/me/connections/${connection.id}/token`).send({}); + expect(denied.status).toBe(403); + expect(denied.body).toMatchObject({ code: "grant_audience_denied", grantId: grant!.id }); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); + + it.each(["suspend", "archive", "remove"] as const)( + "keeps an organization audience fail-closed when empty replacement races with member %s", + async (cleanupKind) => { + const company = await createCompany(db); + await grantBoardUser(db, company.id, "owner", [], "owner"); + await grantBoardUser(db, company.id, "previous-user", [], "member"); + await grantBoardUser(db, company.id, "departing-user", [], "member"); + const departingMembership = await db.select().from(companyMemberships).where(and( + eq(companyMemberships.companyId, company.id), + eq(companyMemberships.principalType, "user"), + eq(companyMemberships.principalId, "departing-user"), + )).then((rows) => rows[0]!); + const agent = await createAgent(db, company.id); + const { run } = await createIssueAndRun(db, company.id, agent.id); + const { connection } = await createBrokerConnection(db, company.id); + await allowConnectionForAgent(db, company.id, agent.id, connection.id); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "organization", + credentialSecretRefs: connection.credentialSecretRefs, + status: "active", + isDefault: true, + }).returning().then((rows) => rows[0]!); + await db.insert(connectionGrantMembers).values({ + companyId: company.id, + grantId: grant.id, + subjectType: "user", + subjectId: "previous-user", + }); + + const firstReplacementDb = createDb(tempDb!.connectionString, { maxConnections: 1 }); + const replacementDb = createDb(tempDb!.connectionString, { maxConnections: 1 }); + const cleanupDb = createDb(tempDb!.connectionString, { maxConnections: 1 }); + const replacementService = createTestToolAccessService(replacementDb); + let releaseFirstReplacement!: () => void; + const firstReplacementMayCommit = new Promise((resolve) => { + releaseFirstReplacement = resolve; + }); + let firstReplacementStaged!: () => void; + const firstReplacementIsStaged = new Promise((resolve) => { + firstReplacementStaged = resolve; + }); + let releaseCleanup!: () => void; + const cleanupMayFinish = new Promise((resolve) => { + releaseCleanup = resolve; + }); + let membershipLocked!: () => void; + const membershipIsLocked = new Promise((resolve) => { + membershipLocked = resolve; + }); + + const firstReplacement = firstReplacementDb.transaction(async (tx) => { + const service = createTestToolAccessService(tx as unknown as ReturnType); + await service.replaceConnectionGrantMembers(connection.id, grant.id, ["departing-user"]); + firstReplacementStaged(); + await firstReplacementMayCommit; + }); + await firstReplacementIsStaged; + + const cleanup = cleanupDb.transaction(async (tx) => { + await tx.select({ id: companyMemberships.id }) + .from(companyMemberships) + .where(eq(companyMemberships.id, departingMembership.id)) + .for("update"); + membershipLocked(); + await cleanupMayFinish; + + const access = accessService(tx as unknown as ReturnType); + if (cleanupKind === "suspend") { + await access.updateMemberAndPermissions( + company.id, + departingMembership.id, + { status: "suspended", grants: [] }, + "owner", + ); + } else if (cleanupKind === "archive") { + await access.archiveMember(company.id, departingMembership.id); + } else { + await access.setUserCompanyAccess("departing-user", []); + } + }); + + // The first replacement still owns the departing member lock, so this + // gives cleanup time to queue for that lock before the empty replacement + // queues for the grant lock. + await new Promise((resolve) => setTimeout(resolve, 50)); + let replacementSettled = false; + const replacement = replacementService + .replaceConnectionGrantMembers(connection.id, grant.id, []) + .then( + (value) => ({ value, error: null }), + (error: unknown) => ({ value: null, error }), + ) + .finally(() => { + replacementSettled = true; + }); + await new Promise((resolve) => setTimeout(resolve, 50)); + expect(replacementSettled).toBe(false); + + releaseFirstReplacement(); + await firstReplacement; + await membershipIsLocked; + expect(replacementSettled).toBe(false); + releaseCleanup(); + await cleanup; + const replacementResult = await replacement; + expect(replacementResult.value).toBeNull(); + expect(replacementResult.error).toEqual(expect.objectContaining({ + message: "Replace inactive audience members before widening access to the whole company", + status: 409, + details: { + code: "audience_widening_blocked", + inactiveUserIds: ["departing-user"], + }, + })); + expect(await db.select().from(connectionGrantMembers).where(eq( + connectionGrantMembers.grantId, + grant.id, + ))).toEqual([ + expect.objectContaining({ subjectType: "user", subjectId: "departing-user" }), + ]); + + const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue( + mcpHttpResponse({ token: "unexpected-company-wide-token" }), + ); + const denied = await request(createRouteApp( + db, + agentJwtActor(company.id, agent.id, run.id), + )).post(`/api/agents/me/connections/${connection.id}/token`).send({}); + expect(denied.status).toBe(403); + expect(denied.body).toMatchObject({ code: "grant_audience_denied", grantId: grant.id }); + expect(fetchMock).not.toHaveBeenCalled(); + }, + ); + it("returns daily connection usage buckets", async () => { const company = await createCompany(db); const { connection } = await createBrokerConnection(db, company.id); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); await db.insert(connectionTokenIssuances).values({ companyId: company.id, applicationId: connection.applicationId, @@ -1072,7 +1518,7 @@ describeEmbeddedPostgres("tool access service", () => { it("quarantines new or changed catalog entries during active opt-in catalog refresh", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const fetchMock = mockToolsList([ { name: "search_notes", @@ -1150,7 +1596,7 @@ describeEmbeddedPostgres("tool access service", () => { it("sends the MCP Streamable HTTP Accept header and decodes an SSE catalog response", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); // Emulate a spec-compliant Streamable HTTP server: 406 unless the request // advertises `Accept: application/json, text/event-stream`, and an @@ -1206,7 +1652,7 @@ describeEmbeddedPostgres("tool access service", () => { it("registers an approved local stdio template and exposes its runtime slot", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connection = await service.createConnection(company.id, { name: "Local echo fixture", @@ -1308,7 +1754,7 @@ describeEmbeddedPostgres("tool access service", () => { it("launches local stdio slots only through active admin-defined templates", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); await service.createStdioCommandTemplate(company.id, { templateId: "admin.local-echo", @@ -1362,7 +1808,7 @@ describeEmbeddedPostgres("tool access service", () => { ["authenticated/public", { deploymentMode: "authenticated" as const, deploymentExposure: "public" as const }], ])("always blocks link-local remote HTTP endpoints in %s before fetch", async (_label, deployment) => { const company = await createCompany(db); - const service = toolAccessService(db, deployment); + const service = createTestToolAccessService(db, deployment); const fetchSpy = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("fetch should not be called")); try { @@ -1384,7 +1830,7 @@ describeEmbeddedPostgres("tool access service", () => { it("creates profiles with entries, binds them to agents, and resolves effective allowed tools", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [agent] = await db.insert(agents).values({ companyId: company.id, name: `Profile Agent ${randomUUID()}`, @@ -1474,7 +1920,7 @@ describeEmbeddedPostgres("tool access service", () => { ["pagesTokenExchangeUrl", { pagesTokenExchangeUrl: "http://169.254.169.254/token" }], ])("rejects a link-local %s when a remote connection is created", async (_field, brokerConfig) => { const company = await createCompany(db); - const service = toolAccessService(db, { + const service = createTestToolAccessService(db, { deploymentMode: "authenticated", deploymentExposure: "public", }); @@ -1494,7 +1940,7 @@ describeEmbeddedPostgres("tool access service", () => { it("rejects a link-local token broker when a remote connection is updated", async () => { const company = await createCompany(db); - const service = toolAccessService(db, { + const service = createTestToolAccessService(db, { deploymentMode: "authenticated", deploymentExposure: "public", }); @@ -1523,7 +1969,7 @@ describeEmbeddedPostgres("tool access service", () => { it("implicitly allowlists the configured Pages API host for internal token brokers", async () => { vi.stubEnv("PAPERCLIP_PAGES_API_URL", "http://127.0.0.1:8787"); const company = await createCompany(db); - const service = toolAccessService(db, { + const service = createTestToolAccessService(db, { deploymentMode: "authenticated", deploymentExposure: "public", }); @@ -2192,7 +2638,7 @@ describeEmbeddedPostgres("tool access service", () => { }, ]).returning(); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const profile = await service.createProfile(company.id, { profileKey: `profile-${randomUUID()}`, name: "All except write tools", @@ -2277,7 +2723,7 @@ describeEmbeddedPostgres("tool access service", () => { schemaHash: randomUUID(), }); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [companyProfile, agentProfile] = await Promise.all([ service.createProfile(company.id, { profileKey: `company-default-${randomUUID()}`, @@ -2354,7 +2800,7 @@ describeEmbeddedPostgres("tool access service", () => { }, ]); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [companyProfile, agentProfile] = await Promise.all([ service.createProfile(company.id, { profileKey: `company-read-${randomUUID()}`, @@ -2388,7 +2834,7 @@ describeEmbeddedPostgres("tool access service", () => { adapterConfig: {}, runtimeConfig: {}, }).returning(); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const profile = await service.createProfile(company.id, { profileKey: `profile-${randomUUID()}`, name: "Email tools source", @@ -2432,7 +2878,7 @@ describeEmbeddedPostgres("tool access service", () => { adapterConfig: {}, runtimeConfig: {}, }).returning(); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const profile = await service.createProfile(company.id, { profileKey: `profile-${randomUUID()}`, name: "Delete source", @@ -2483,7 +2929,7 @@ describeEmbeddedPostgres("tool access service", () => { adapterConfig: {}, runtimeConfig: {}, }).returning(); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const profile = await service.createProfile(company.id, { profileKey: `route-profile-${randomUUID()}`, name: "Route profile", @@ -2538,7 +2984,7 @@ describeEmbeddedPostgres("tool access service", () => { it("returns 404 for cross-company profile routes and missing profiles", async () => { const allowedCompany = await createCompany(db); const otherCompany = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const profile = await service.createProfile(otherCompany.id, { profileKey: `other-profile-${randomUUID()}`, name: "Other company profile", @@ -2600,7 +3046,7 @@ describeEmbeddedPostgres("tool access service", () => { it("returns 404 for cross-company connection routes, including instance admins", async () => { const allowedCompany = await createCompany(db); const otherCompany = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connection = await service.createConnection(otherCompany.id, { name: "Other company connection", transport: "mcp_remote", @@ -2687,7 +3133,7 @@ describeEmbeddedPostgres("tool access service", () => { it("installs the safe example fixture idempotently and smokes allow, deny, and audit paths", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const before = await service.listExamples(company.id); expect(before).toEqual([ @@ -2862,6 +3308,10 @@ describeEmbeddedPostgres("tool access service", () => { const res = await request(app).get(`/api/companies/${company.id}/tools/gallery`); expect(res.status).toBe(200); + expect(res.body.capabilities).toEqual({ + canSetCompanyInstall: true, + companyInstallReason: null, + }); expect(res.body.apps.map((app: { slug: string }) => app.slug)).toEqual([ "zapier", "github", @@ -2914,6 +3364,19 @@ describeEmbeddedPostgres("tool access service", () => { ); }); + it("returns server-derived create capabilities for a non-manager member", async () => { + const company = await createCompany(db); + const app = createRouteApp(db, boardSessionActor(company.id, "member")); + + const res = await request(app).get(`/api/companies/${company.id}/tools/gallery`); + + expect(res.status).toBe(200); + expect(res.body.capabilities).toEqual({ + canSetCompanyInstall: false, + companyInstallReason: "Only someone who can configure this connection can choose this.", + }); + }); + it("previews remote mcp.json headers as secret replacement fields without echoing values", async () => { const company = await createCompany(db); const app = createRouteApp(db); @@ -2953,7 +3416,7 @@ describeEmbeddedPostgres("tool access service", () => { it("creates link-based MCP connections with imported header secrets before catalog review", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const fetchMock = vi.spyOn(globalThis, "fetch").mockImplementation(async (_url, init) => { const headers = init?.headers as Record; expect(headers.Authorization).toBe("Bearer imported-token"); @@ -3011,7 +3474,7 @@ describeEmbeddedPostgres("tool access service", () => { id: "paperclip-catalog-refresh", result: { tools }, })); - const service = toolAccessService(db, { + const service = createTestToolAccessService(db, { now: () => currentTime, catalogCacheTtlMs: 60_000, }); @@ -3049,9 +3512,80 @@ describeEmbeddedPostgres("tool access service", () => { ); }); + it("commits a 'Just me' key to the caller's own grant and never to the connection or an organization grant", async () => { + const company = await createCompany(db); + const service = createTestToolAccessService(db); + mockToolsList([{ name: "query_insight", annotations: { readOnlyHint: true } }]); + + const connected = await service.connectGalleryApp(company.id, { + galleryKey: "posthog", + connectionMethodKey: "mcp-api-key", + credentialValues: { "credentials.authorization": "phx_personal-secret" }, + configValues: { projectId: "12345", mode: "tools" }, + grantKind: "user", + }, { actorType: "user", actorId: "carol" }); + + const { grants } = await service.listConnectionGrants(connected.connectionId, company.id); + const connection = await service.getConnection(connected.connectionId, company.id); + + // The identity is the caller's, and it is the only grant: creating an + // organization grant first and "moving" the secret later is exactly what the + // design forbids, so there must be no organization grant at all. + expect(grants).toHaveLength(1); + expect(grants[0]).toMatchObject({ kind: "user", subjectUserId: "carol", status: "active" }); + expect(grants[0]!.credentialSecretRefs.length).toBeGreaterThan(0); + expect(grants.some((grant) => grant.kind === "organization")).toBe(false); + + // The personal secret is not reachable as the connection's shared credential. + expect(connection.credentialSecretRefs).toEqual([]); + expect(connection.credentialPolicy).toBe("per_user"); + + // ...and the secret ids the personal grant holds appear nowhere on the row's + // shared secret-ref list, which is what an organization grant would copy. + const personalSecretIds = new Set(grants[0]!.credentialSecretRefs.map((ref) => ref.secretId)); + for (const ref of connection.credentialSecretRefs) { + expect(personalSecretIds.has(ref.secretId)).toBe(false); + } + }); + + it("keeps the shared-credential default when no grant kind is chosen", async () => { + const company = await createCompany(db); + const service = createTestToolAccessService(db); + mockToolsList([{ name: "query_insight", annotations: { readOnlyHint: true } }]); + + const connected = await service.connectGalleryApp(company.id, { + galleryKey: "posthog", + connectionMethodKey: "mcp-api-key", + credentialValues: { "credentials.authorization": "phx_shared-secret" }, + configValues: { projectId: "12345", mode: "tools" }, + }, { actorType: "user", actorId: "board" }); + + const { grants } = await service.listConnectionGrants(connected.connectionId, company.id); + const connection = await service.getConnection(connected.connectionId, company.id); + + expect(grants).toHaveLength(1); + expect(grants[0]).toMatchObject({ kind: "organization", isDefault: true }); + expect(grants[0]!.credentialSecretRefs.length).toBeGreaterThan(0); + expect(connection.credentialPolicy).toBe("shared"); + }); + + it("refuses a personal identity when no named user is making the request", async () => { + const company = await createCompany(db); + const service = createTestToolAccessService(db); + mockToolsList([{ name: "query_insight", annotations: { readOnlyHint: true } }]); + + await expect(service.connectGalleryApp(company.id, { + galleryKey: "posthog", + connectionMethodKey: "mcp-api-key", + credentialValues: { "credentials.authorization": "phx_agent-secret" }, + configValues: { projectId: "12345", mode: "tools" }, + grantKind: "user", + }, { actorType: "agent", actorId: "agent-1" })).rejects.toMatchObject({ status: 400 }); + }); + it("requires an explicit PostHog method and projects validated project filters", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); await expect(service.connectGalleryApp(company.id, { galleryKey: "posthog", @@ -3110,7 +3644,7 @@ describeEmbeddedPostgres("tool access service", () => { it("stores approved class-3 credential refs on thin tool connections", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [secret] = await db.insert(companySecrets).values({ companyId: company.id, key: `discord.bot_token.${randomUUID()}`, @@ -3160,7 +3694,7 @@ describeEmbeddedPostgres("tool access service", () => { it("rejects class-3 tool connection refs outside the enumerated allowlist", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [application] = await db.insert(toolApplications).values({ companyId: company.id, applicationKey: `blocked-${randomUUID()}`, @@ -3201,7 +3735,7 @@ describeEmbeddedPostgres("tool access service", () => { it("rejects Google Sheets gallery connects that claim a spreadsheet bound to another company", async () => { const companyA = await createCompany(db); const companyB = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); vi.stubEnv("GOOGLE_SHEETS_SERVICE_ACCOUNT_JSON", JSON.stringify({ client_email: "robot@example.iam.gserviceaccount.com", })); @@ -3229,7 +3763,7 @@ describeEmbeddedPostgres("tool access service", () => { it("stores Google Sheets catalog input schemas from the approved stdio template", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); vi.stubEnv("GOOGLE_SHEETS_SERVICE_ACCOUNT_JSON", JSON.stringify({ client_email: "robot@example.iam.gserviceaccount.com", })); @@ -3239,7 +3773,6 @@ describeEmbeddedPostgres("tool access service", () => { name: "Company sheets", configValues: { allowedSpreadsheetIds: ["sheet-with-inputs"] }, }, { actorType: "user", actorId: "board" }); - const descriptions = Object.fromEntries(connect.catalog.map((entry) => [entry.toolName, entry.description])); expect(descriptions).toMatchObject({ list_spreadsheets: "List the Google Sheets spreadsheets configured in this connection allowlist.", @@ -3296,7 +3829,7 @@ describeEmbeddedPostgres("tool access service", () => { it("rejects raw Google Sheets connection patches that claim another company's spreadsheet", async () => { const companyA = await createCompany(db); const companyB = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const app = createRouteApp(db); vi.stubEnv("GOOGLE_SHEETS_SERVICE_ACCOUNT_JSON", JSON.stringify({ client_email: "robot@example.iam.gserviceaccount.com", @@ -3344,7 +3877,7 @@ describeEmbeddedPostgres("tool access service", () => { it("tags a pause PATCH with a lifecycle activity row the Activity tab can surface", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const app = createRouteApp(db); const [application] = await db.insert(toolApplications).values({ companyId: company.id, @@ -3382,7 +3915,7 @@ describeEmbeddedPostgres("tool access service", () => { it("allows same-company Google Sheets updates and derives the env mirror from the allowlist", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); vi.stubEnv("GOOGLE_SHEETS_SERVICE_ACCOUNT_JSON", JSON.stringify({ client_email: "robot@example.iam.gserviceaccount.com", })); @@ -3425,7 +3958,7 @@ describeEmbeddedPostgres("tool access service", () => { const company = await createCompany(db); const agent = await createAgent(db, company.id); const { issue, run } = await createIssueAndRun(db, company.id, agent.id); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connected = await service.connectGalleryApp(company.id, { galleryKey: "slack", name: "Slack user auth" }); const workspaceStarted = await service.startOAuth(company.id, connected.connectionId, { @@ -3481,7 +4014,7 @@ describeEmbeddedPostgres("tool access service", () => { issueId: issue.id, kind: "request_confirmation", status: "pending", - title: "Connect your account", + title: "Connect your Slack to continue", }); expect(interaction.payload).toMatchObject({ target: { href: started.authorizationUrl } }); @@ -3499,10 +4032,44 @@ describeEmbeddedPostgres("tool access service", () => { expect(grant).toMatchObject({ kind: "user", status: "active" }); expect(grant.credentialSecretRefs.map((ref) => ref.configPath).sort()).toEqual(["oauth.access_token", "oauth.refresh_token"]); expect(grant.credentialSecretRefs.map((ref) => ref.secretId).sort()).not.toEqual(workspaceSecretIds); + const personalSecrets = await db.select().from(companySecrets).where( + inArray(companySecrets.id, grant.credentialSecretRefs.map((ref) => ref.secretId)), + ); + expect(personalSecrets).toHaveLength(2); + expect(personalSecrets).toEqual(expect.arrayContaining([ + expect.objectContaining({ scope: "user", ownerUserId: "user-for-run" }), + ])); + expect(personalSecrets.every((secret) => secret.userSecretDefinitionId !== null)).toBe(true); const [unchangedConnection] = await db.select().from(toolConnections).where(eq(toolConnections.id, connected.connectionId)); expect(unchangedConnection.credentialSecretRefs.map((ref) => ref.secretId).sort()).toEqual(workspaceSecretIds); const [resolved] = await db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.id, interaction.id)); expect(resolved).toMatchObject({ status: "accepted", result: { version: 1, outcome: "accepted" } }); + + const versionCountBeforeSuspension = (await db.select().from(companySecretVersions).where( + inArray(companySecretVersions.secretId, grant.credentialSecretRefs.map((ref) => ref.secretId)), + )).length; + const retry = await service.startAuthorizationForAgent({ + companyId: company.id, + connectionId: connected.connectionId, + agentId: agent.id, + runId: run.id, + subjectUserId: "user-for-run", + scopes: ["users:read"], + redirectUri: "https://paperclip.example/api/tools/oauth/callback", + }); + await db.update(companyMemberships).set({ status: "suspended" }).where(and( + eq(companyMemberships.companyId, company.id), + eq(companyMemberships.principalId, "user-for-run"), + )); + await expect(service.completeOAuthCallback({ + state: new URL(retry.authorizationUrl).searchParams.get("state")!, + code: "user-authorization-code", + redirectUri: "https://paperclip.example/api/tools/oauth/callback", + actor: { actorType: "user", actorId: "user-for-run" }, + })).rejects.toMatchObject({ status: 403 }); + expect((await db.select().from(companySecretVersions).where( + inArray(companySecretVersions.secretId, grant.credentialSecretRefs.map((ref) => ref.secretId)), + )).length).toBe(versionCountBeforeSuspension); }); it("starts and completes OAuth app sign-in with PKCE state and secret-backed tokens", async () => { @@ -3628,7 +4195,7 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_ID", "slack-client-id"); vi.stubEnv("PAPERCLIP_PUBLIC_URL", "http://paperclip.test"); const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connect = await service.connectGalleryApp( company.id, { galleryKey: "slack", name: "Slack reauth" }, @@ -3672,7 +4239,7 @@ describeEmbeddedPostgres("tool access service", () => { it("requires non-viewer board access to finish app activation and bind profiles", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); mockToolsList([ { name: "kv_get", @@ -3686,6 +4253,7 @@ describeEmbeddedPostgres("tool access service", () => { name: "Viewer finish blocked", credentialValues: { "headers.Authorization": "Bearer imported-token" }, }, { actorType: "user", actorId: "board" }); + const bindingsBefore = await db.select({ id: toolProfileBindings.id }).from(toolProfileBindings); const viewerApp = createRouteApp(db, boardSessionActor(company.id, "viewer", "viewer-user")); await request(viewerApp) @@ -3700,7 +4268,7 @@ describeEmbeddedPostgres("tool access service", () => { const [connection] = await db.select().from(toolConnections).where(eq(toolConnections.id, connect.connectionId)); expect(connection.status).toBe("draft"); expect(connection.enabled).toBe(false); - await expect(db.select().from(toolProfileBindings)).resolves.toHaveLength(0); + await expect(db.select({ id: toolProfileBindings.id }).from(toolProfileBindings)).resolves.toEqual(bindingsBefore); }); it("binds OAuth callback completion to the initiating board session", async () => { @@ -3708,7 +4276,7 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_SECRET", "slack-client-secret"); vi.stubEnv("PAPERCLIP_PUBLIC_URL", "http://paperclip.test"); const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const initiatingActor = boardSessionActor(company.id, "operator", "oauth-operator"); const connect = await service.connectGalleryApp( company.id, @@ -3797,7 +4365,7 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_CLIENT_ID", ""); vi.stubEnv("PAPERCLIP_TOOL_OAUTH_CLIENT_SECRET", ""); const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connected = await service.connectGalleryApp(company.id, { galleryKey: "notion", name: "Notion DCR", @@ -3920,7 +4488,7 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_CLIENT_ID", ""); vi.stubEnv("PAPERCLIP_TOOL_OAUTH_CLIENT_SECRET", ""); const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connected = await service.connectGalleryApp(company.id, { galleryKey: "notion", name: `Notion invalid DCR ${field}`, @@ -3976,7 +4544,7 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_NOTION_CLIENT_ID", ""); vi.stubEnv("PAPERCLIP_TOOL_OAUTH_CLIENT_ID", ""); const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connected = await service.connectGalleryApp(company.id, { galleryKey: "notion", name: "Notion invalid origin", @@ -4001,8 +4569,8 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_ID", "slack-client-id"); vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_SECRET", "slack-client-secret"); const company = await createCompany(db); - const service = toolAccessService(db); - const concurrentService = toolAccessService(db); + const service = createTestToolAccessService(db); + const concurrentService = createTestToolAccessService(db); const connect = await service.connectGalleryApp(company.id, { galleryKey: "slack", name: "Slack refresh" }); const start = await service.startOAuth(company.id, connect.connectionId, { @@ -4106,7 +4674,7 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_ID", "slack-client-id"); vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_SECRET", "slack-client-secret"); const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connect = await service.connectGalleryApp(company.id, { galleryKey: "slack", name: "Slack invalid grant" }); const start = await service.startOAuth(company.id, connect.connectionId, { redirectUri: "http://paperclip.test/api/tools/oauth/callback", @@ -4145,7 +4713,6 @@ describeEmbeddedPostgres("tool access service", () => { }, }, }).where(eq(toolConnections.id, connect.connectionId)); - let refreshCallCount = 0; fetchMock.mockImplementation(async (url, init) => { const href = String(url); @@ -4197,7 +4764,7 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_ID", "slack-client-id"); vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_SECRET", "slack-client-secret"); const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connect = await service.connectGalleryApp(company.id, { galleryKey: "slack", name: "Slack stale invalid grant", @@ -4280,7 +4847,7 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_ID", "slack-client-id"); vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_SECRET", "slack-client-secret"); const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const fixture = await createOAuthConnection(db, company.id); const refreshSecret = await secretService(db).create(company.id, { provider: "local_encrypted", @@ -4331,7 +4898,7 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_M2M_CLIENT_ID", "m2m-client-id"); vi.stubEnv("PAPERCLIP_TOOL_OAUTH_M2M_CLIENT_SECRET", "m2m-client-secret"); const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connection = await service.createConnection(company.id, { name: "Machine OAuth", transport: "mcp_remote", @@ -4392,7 +4959,7 @@ describeEmbeddedPostgres("tool access service", () => { vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_ID", "slack-client-id"); vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_SECRET", "slack-client-secret"); const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connect = await service.connectGalleryApp(company.id, { galleryKey: "slack", name: "Slack no refresh" }); const start = await service.startOAuth(company.id, connect.connectionId, { redirectUri: "http://paperclip.test/api/tools/oauth/callback", @@ -4660,7 +5227,7 @@ describeEmbeddedPostgres("tool access service", () => { }, ]).returning(); - const list = await toolAccessService(db).listActionRequests(company.id, "pending"); + const list = await createTestToolAccessService(db).listActionRequests(company.id, "pending"); const rows = await db.select().from(toolActionRequests); const statusById = new Map(rows.map((row) => [row.id, row.status])); @@ -4828,7 +5395,7 @@ describeEmbeddedPostgres("tool access service", () => { it("returns addedAt for auto-allowed effective profile tools without pending review state", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [agent] = await db.insert(agents).values({ companyId: company.id, name: "Tool User", @@ -4999,7 +5566,7 @@ describeEmbeddedPostgres("tool access service", () => { it("rolls back app connect drafts when health check fails", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("network down")); await expect(service.connectGalleryApp(company.id, { @@ -5014,7 +5581,7 @@ describeEmbeddedPostgres("tool access service", () => { it("reuses and revives an existing application when connecting with applicationId", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); mockToolsList([ { name: "read_items", @@ -5061,7 +5628,7 @@ describeEmbeddedPostgres("tool access service", () => { it("allows multiple same-named connections on one application", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); mockToolsList([ { name: "read_items", @@ -5091,7 +5658,7 @@ describeEmbeddedPostgres("tool access service", () => { it("does not delete a reused application when the connect rolls back", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); mockToolsList([ { name: "read_items", @@ -5126,7 +5693,7 @@ describeEmbeddedPostgres("tool access service", () => { it("connects pasted links with an optional secret-backed app key", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const fetchMock = mockToolsList([ { name: "read_items", @@ -5346,7 +5913,7 @@ describeEmbeddedPostgres("tool access service", () => { it("blocks Smoke Lab OAuth issuer URLs from the normal tool OAuth secret pipeline", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const smokeAuthorizeUrl = `http://127.0.0.1:3100/api/companies/${company.id}/smoke-lab/oauth/authorize`; const smokeTokenUrl = `http://127.0.0.1:3100/api/companies/${company.id}/smoke-lab/oauth/token`; const [application] = await db.insert(toolApplications).values({ @@ -5419,7 +5986,7 @@ describeEmbeddedPostgres("tool access service", () => { it("starts OAuth only for the marked Smoke Lab HTTP fixture", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [application] = await db.insert(toolApplications).values({ companyId: company.id, applicationKey: "paperclip.smoke-lab.http-fixture", @@ -5507,7 +6074,7 @@ describeEmbeddedPostgres("tool access service", () => { it("connects gallery apps and finishes access profiles, bindings, and ask-first policies", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const fetchMock = mockToolsList([ { name: "list_zaps", @@ -5732,7 +6299,7 @@ describeEmbeddedPostgres("tool access service", () => { it("enables newly discovered tools after setup while preserving tools explicitly turned off", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const fetchMock = mockToolsList([ { name: "list_zaps", annotations: { readOnlyHint: true } }, { name: "update_zap", annotations: { readOnlyHint: false } }, @@ -5793,7 +6360,7 @@ describeEmbeddedPostgres("tool access service", () => { const company = await createCompany(db); const otherCompany = await createCompany(db); const agent = await createAgent(db, company.id); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [application] = await db.insert(toolApplications).values({ companyId: company.id, applicationKey: `app-gallery:notion:${randomUUID()}`, @@ -5898,7 +6465,7 @@ describeEmbeddedPostgres("tool access service", () => { it("rolls back gallery app finish when a later write fails after clearing profile state", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); mockToolsList([ { name: "list_zaps", @@ -5990,8 +6557,8 @@ describeEmbeddedPostgres("tool access service", () => { it("reconnects a gallery app by rotating the existing credential in place (PAP-10859)", async () => { const company = await createCompany(db); - const service = toolAccessService(db); - mockToolsList([ + const service = createTestToolAccessService(db); + const fetchMock = mockToolsList([ { name: "list_zaps", description: "List", inputSchema: { type: "object", properties: {} }, annotations: { readOnlyHint: true } }, { name: "update_zap", description: "Update", inputSchema: { type: "object", properties: {} }, annotations: { readOnlyHint: false } }, ]); @@ -6024,6 +6591,17 @@ describeEmbeddedPostgres("tool access service", () => { config: { ...before.config, quarantineNewEntries: true }, transportConfig: { ...before.transportConfig, quarantineNewEntries: true }, }).where(eq(toolConnections.id, connect.connectionId)); + fetchMock.mockResolvedValue(mcpHttpResponse({ + jsonrpc: "2.0", + id: "paperclip-catalog-refresh", + result: { + tools: [ + { name: "list_zaps", description: "List", inputSchema: { type: "object", properties: {} }, annotations: { readOnlyHint: true } }, + { name: "update_zap", description: "Update", inputSchema: { type: "object", properties: {} }, annotations: { readOnlyHint: false } }, + { name: "delete_zap", description: "Delete", inputSchema: { type: "object", properties: {} }, annotations: { destructiveHint: true } }, + ], + }, + })); await expect( service.reconnectGalleryApp(connect.connectionId, company.id, { credentialValues: {} }, { actorType: "user", actorId: "board" }), @@ -6051,6 +6629,7 @@ describeEmbeddedPostgres("tool access service", () => { expect(catalogAfterReconnect).toEqual(expect.arrayContaining([ expect.objectContaining({ id: listEntry.id, status: "active", quarantineReason: null }), expect.objectContaining({ id: updateEntry.id, status: "active", quarantineReason: null }), + expect.objectContaining({ toolName: "delete_zap", status: "active", riskLevel: "destructive" }), ])); const profileEntriesAfterReconnect = await db.select().from(toolProfileEntries).where( eq(toolProfileEntries.profileId, finished.profile.id), @@ -6059,15 +6638,26 @@ describeEmbeddedPostgres("tool access service", () => { expect.objectContaining({ catalogEntryId: listEntry.id, effect: "include" }), expect.objectContaining({ catalogEntryId: updateEntry.id, effect: "include" }), ])); - await expect(db.select().from(toolPolicies).where(and( + const policiesAfterReconnect = await db.select().from(toolPolicies).where(and( eq(toolPolicies.companyId, company.id), eq(toolPolicies.enabled, true), - ))).resolves.toHaveLength(0); + )); + expect(policiesAfterReconnect).toHaveLength(2); + expect(policiesAfterReconnect).toEqual(expect.arrayContaining([ + expect.objectContaining({ + policyType: "require_approval", + selectors: { catalogEntryId: updateEntry.id }, + }), + expect.objectContaining({ + policyType: "require_approval", + selectors: { catalogEntryId: catalogAfterReconnect.find((entry) => entry.toolName === "delete_zap")!.id }, + }), + ])); }); it("stops and restarts local stdio runtime slots through the board service", async () => { const company = await createCompany(db); - const service = toolAccessService(db, { now: () => new Date("2026-06-06T01:00:00.000Z") }); + const service = createTestToolAccessService(db, { now: () => new Date("2026-06-06T01:00:00.000Z") }); const connection = await service.createConnection(company.id, { name: "Restartable local fixture", @@ -6126,7 +6716,7 @@ describeEmbeddedPostgres("tool access service", () => { it("exposes board runtime slot stop and restart endpoints", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const app = createRouteApp(db); const connection = await service.createConnection(company.id, { name: "Route local fixture", @@ -6165,7 +6755,7 @@ describeEmbeddedPostgres("tool access service", () => { it("requires tools:manage_runtime for company-scoped runtime slot routes", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const userId = `runtime-operator-${randomUUID()}`; await db.insert(companyMemberships).values({ companyId: company.id, @@ -6235,7 +6825,7 @@ describeEmbeddedPostgres("tool access service", () => { it("updates tool applications through the board route and records activity", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const app = createRouteApp(db); const application = await service.createApplication(company.id, { name: "Editable app", @@ -6267,7 +6857,7 @@ describeEmbeddedPostgres("tool access service", () => { it("returns 409 instead of 500 when an application update collides with a duplicate name", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const app = createRouteApp(db); await service.createApplication(company.id, { name: "Existing app", type: "mcp_http" }); const application = await service.createApplication(company.id, { name: "Editable app", type: "mcp_http" }); @@ -6285,7 +6875,7 @@ describeEmbeddedPostgres("tool access service", () => { it("returns 404 for cross-company application updates and missing applications", async () => { const allowedCompany = await createCompany(db); const otherCompany = await createCompany(db); - const application = await toolAccessService(db).createApplication(otherCompany.id, { + const application = await createTestToolAccessService(db).createApplication(otherCompany.id, { name: "Other company app", type: "mcp_http", }); @@ -6319,7 +6909,7 @@ describeEmbeddedPostgres("tool access service", () => { it("keeps direct application and connection mutation routes viewer-safe", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const application = await service.createApplication(company.id, { name: "Viewer guarded app", type: "mcp_http", @@ -6365,10 +6955,68 @@ describeEmbeddedPostgres("tool access service", () => { } }); + it("does not expose another user's draft connection to a regular member", async () => { + const company = await createCompany(db); + const service = createTestToolAccessService(db); + const application = await service.createApplication(company.id, { + name: "Shared OAuth app", + type: "mcp_http", + }); + const otherDraft = await service.createConnection(company.id, { + applicationId: application.id, + name: "Other user's draft", + transport: "mcp_remote", + authKind: "oauth", + status: "draft", + config: { url: "https://other-draft.example/mcp" }, + }, { actorType: "user", actorId: "other-user" }); + const ownDraft = await service.createConnection(company.id, { + applicationId: application.id, + name: "Member's draft", + transport: "mcp_remote", + authKind: "oauth", + status: "draft", + config: { url: "https://own-draft.example/mcp" }, + }, { actorType: "user", actorId: "member-user" }); + const activeConnection = await service.createConnection(company.id, { + applicationId: application.id, + name: "Active connection", + transport: "mcp_remote", + authKind: "oauth", + status: "active", + enabled: true, + config: { url: "https://active.example/mcp" }, + }, { actorType: "user", actorId: "other-user" }); + + const memberApp = createRouteApp(db, boardSessionActor(company.id, "member", "member-user")); + const memberRes = await request(memberApp) + .get(`/api/companies/${company.id}/tools/connections`) + .expect(200); + + expect(memberRes.body.connections).toHaveLength(2); + expect(memberRes.body.connections).toEqual(expect.arrayContaining([ + expect.objectContaining({ id: ownDraft.id }), + expect.objectContaining({ id: activeConnection.id }), + ])); + expect(memberRes.body.connections).not.toEqual(expect.arrayContaining([ + expect.objectContaining({ id: otherDraft.id }), + ])); + + const ownerApp = createRouteApp(db, boardSessionActor(company.id, "owner", "owner-user")); + const ownerRes = await request(ownerApp) + .get(`/api/companies/${company.id}/tools/connections`) + .expect(200); + expect(ownerRes.body.connections).toEqual(expect.arrayContaining([ + expect.objectContaining({ id: otherDraft.id }), + expect.objectContaining({ id: ownDraft.id }), + expect.objectContaining({ id: activeConnection.id }), + ])); + }); + it("keeps direct profile and policy mutation routes viewer-safe", async () => { const company = await createCompany(db); const agent = await createAgent(db, company.id); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const profile = await service.createProfile(company.id, { profileKey: `viewer-guarded-profile-${randomUUID()}`, name: "Viewer guarded profile", @@ -6454,7 +7102,7 @@ describeEmbeddedPostgres("tool access service", () => { it("deletes an application with zero connections and records activity", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const app = createRouteApp(db); const application = await service.createApplication(company.id, { name: "Deletable app", @@ -6482,7 +7130,7 @@ describeEmbeddedPostgres("tool access service", () => { it("returns 409 and keeps the application when it still has connections", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const app = createRouteApp(db); const connection = await service.createConnection(company.id, { name: "Guarded connection", @@ -6503,7 +7151,7 @@ describeEmbeddedPostgres("tool access service", () => { it("archives the application when its last connection is removed", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const app = createRouteApp(db); const connection = await service.createConnection(company.id, { name: "Single connection", @@ -6543,7 +7191,7 @@ describeEmbeddedPostgres("tool access service", () => { it("keeps the application active when another connection remains", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const app = createRouteApp(db); const application = await service.createApplication(company.id, { name: "Shared app", @@ -6583,7 +7231,7 @@ describeEmbeddedPostgres("tool access service", () => { it("keeps normalized connection UIDs unique", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const firstApplication = await service.createApplication(company.id, { name: "First UID app", type: "mcp_http", @@ -6613,7 +7261,7 @@ describeEmbeddedPostgres("tool access service", () => { it("fails closed at the database when a connection races an application delete (no silent cascade)", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connection = await service.createConnection(company.id, { name: "Racy connection", transport: "mcp_remote", @@ -6643,7 +7291,7 @@ describeEmbeddedPostgres("tool access service", () => { it("still cascades application + connection deletes when the owning company is removed", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connection = await service.createConnection(company.id, { name: "Company-scoped connection", transport: "mcp_remote", @@ -6671,7 +7319,7 @@ describeEmbeddedPostgres("tool access service", () => { it("returns 404 for cross-company application deletes and missing applications", async () => { const allowedCompany = await createCompany(db); const otherCompany = await createCompany(db); - const application = await toolAccessService(db).createApplication(otherCompany.id, { + const application = await createTestToolAccessService(db).createApplication(otherCompany.id, { name: "Other company app", type: "mcp_http", }); @@ -6818,7 +7466,7 @@ describeEmbeddedPostgres("tool access service", () => { metadata: { interactionId: interaction.id }, }).returning(); - const lookup = await toolAccessService(db).getRunDecisionLookup(company.id, run.id); + const lookup = await createTestToolAccessService(db).getRunDecisionLookup(company.id, run.id); expect(lookup).toMatchObject({ runId: run.id, @@ -6841,7 +7489,7 @@ describeEmbeddedPostgres("tool access service", () => { it("enriches connection activity with issue and approval resolver context", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [agent] = await db.insert(agents).values({ companyId: company.id, name: "CodexCoder", @@ -6983,7 +7631,7 @@ describeEmbeddedPostgres("tool access service", () => { it("surfaces connection lifecycle events on the activity timeline", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [agent] = await db.insert(agents).values({ companyId: company.id, name: "CodexCoder", @@ -7114,7 +7762,7 @@ describeEmbeddedPostgres("tool access service", () => { it("rejects runtime controls for non-local runtime kinds", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const [application] = await db.insert(toolApplications).values({ companyId: company.id, name: "Remote app", @@ -7160,7 +7808,7 @@ describeEmbeddedPostgres("tool access service", () => { it("summarizes runtime health and flags stale slots plus degraded connections", async () => { const company = await createCompany(db); const generatedAt = new Date("2026-06-06T00:00:00.000Z"); - const service = toolAccessService(db, { + const service = createTestToolAccessService(db, { deploymentMode: "authenticated", deploymentExposure: "public", trustedLocalStdioRuntimeHost: null, @@ -7270,7 +7918,7 @@ describeEmbeddedPostgres("tool access service", () => { it("fires runtime health from the durable audit-write failure counter", async () => { const company = await createCompany(db); const generatedAt = new Date("2026-06-06T00:00:00.000Z"); - const service = toolAccessService(db, { now: () => generatedAt }); + const service = createTestToolAccessService(db, { now: () => generatedAt }); await db.insert(toolRuntimeMetricCounters).values({ companyId: company.id, @@ -7294,7 +7942,7 @@ describeEmbeddedPostgres("tool access service", () => { it("does not degrade runtime health for draft or not-enabled setup connections", async () => { const company = await createCompany(db); - const service = toolAccessService(db, { now: () => new Date("2026-06-06T00:00:00.000Z") }); + const service = createTestToolAccessService(db, { now: () => new Date("2026-06-06T00:00:00.000Z") }); const [application] = await db.insert(toolApplications).values({ companyId: company.id, name: "Setup apps", @@ -7348,7 +7996,7 @@ describeEmbeddedPostgres("tool access service", () => { it("rejects enabled local stdio connections in public hosted mode without a trusted runtime host", async () => { const company = await createCompany(db); - const hostedService = toolAccessService(db, { + const hostedService = createTestToolAccessService(db, { deploymentMode: "authenticated", deploymentExposure: "public", trustedLocalStdioRuntimeHost: null, @@ -7365,7 +8013,7 @@ describeEmbeddedPostgres("tool access service", () => { message: expect.stringContaining("cannot be enabled"), }); - const trustedService = toolAccessService(db, { + const trustedService = createTestToolAccessService(db, { deploymentMode: "authenticated", deploymentExposure: "public", trustedLocalStdioRuntimeHost: "trusted-worker-1", @@ -7384,7 +8032,7 @@ describeEmbeddedPostgres("tool access service", () => { it("previews mcp.json imports as draft managed connection records without carrying raw header values", async () => { const company = await createCompany(db); - const preview = await toolAccessService(db).previewMcpJsonImport({ + const preview = await createTestToolAccessService(db).previewMcpJsonImport({ mcpJson: { mcpServers: { github: { @@ -7423,7 +8071,7 @@ describeEmbeddedPostgres("tool access service", () => { it("fails closed when credential secrets cannot be resolved and writes value-free audit", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const connection = await service.createConnection(company.id, { name: "Secret-backed remote", transport: "mcp_remote", @@ -7473,7 +8121,7 @@ describeEmbeddedPostgres("tool access service", () => { it("sweeps enabled active connection health and records failing connections", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("revoked token")); const connection = await service.createConnection(company.id, { name: "Swept remote", @@ -7501,7 +8149,7 @@ describeEmbeddedPostgres("tool access service", () => { it("enriches listConnections with lastUsedAt from the most recent tool-call event", async () => { const company = await createCompany(db); - const service = toolAccessService(db); + const service = createTestToolAccessService(db); const used = await service.createConnection(company.id, { name: "Used remote", @@ -7579,7 +8227,7 @@ describeEmbeddedPostgres("tool access service", () => { const events = await db.select().from(activityLog).where(eq(activityLog.action, "tool_connection.install_access_extended")); expect(events).toHaveLength(1); - const effective = await toolAccessService(db).getEffectiveProfilesForAgent(company.id, agent.id); + const effective = await createTestToolAccessService(db).getEffectiveProfilesForAgent(company.id, agent.id); expect(effective.installedConnections.map((item) => item.id)).toEqual([connection.id]); expect(effective.allowedTools.some((tool) => tool.connectionId === connection.id)).toBe(false); @@ -7608,7 +8256,7 @@ describeEmbeddedPostgres("tool access service", () => { await grantBoardUser(db, company.id, creator.userId!, [], "member"); await grantBoardUser(db, company.id, otherMember.userId!, [], "member"); await grantBoardUser(db, company.id, admin.userId!, [], "admin"); - const connection = await toolAccessService(db).createConnection(company.id, { + const connection = await createTestToolAccessService(db).createConnection(company.id, { name: "Creator-owned connection", transport: "mcp_remote", config: { url: PUBLIC_MCP_FIXTURE_URL }, @@ -7643,7 +8291,7 @@ describeEmbeddedPostgres("tool access service", () => { await grantBoardUser(db, company.id, creator.userId!, [], "member"); await grantBoardUser(db, company.id, member.userId!, ["agents:configure"], "member"); const agent = await createAgent(db, company.id); - const connection = await toolAccessService(db).createConnection(company.id, { + const connection = await createTestToolAccessService(db).createConnection(company.id, { name: "Shared organization connection", transport: "mcp_remote", config: { url: PUBLIC_MCP_FIXTURE_URL }, diff --git a/server/src/__tests__/tool-connection-removal.test.ts b/server/src/__tests__/tool-connection-removal.test.ts index e85b9ac68f..e7bb949c2e 100644 --- a/server/src/__tests__/tool-connection-removal.test.ts +++ b/server/src/__tests__/tool-connection-removal.test.ts @@ -380,7 +380,8 @@ describeEmbeddedPostgres("tool connection removal", () => { expect(removed.removal).toMatchObject({ secretsRevoked: headerSecretIds.length + 3, secretsRetainedShared: 0, - grantsRevoked: 1, + // The default organization grant and the explicit user grant are both revoked. + grantsRevoked: 2, oauthStatesDiscarded: 1, tokenIssuanceHashesCleared: 1, }); @@ -665,8 +666,9 @@ describeEmbeddedPostgres("tool connection removal", () => { const reconnectedSecretIds = after!.credentialSecretRefs.map((ref) => ref.secretId); // Not one of the revoked secrets came back. for (const secretId of originalSecretIds) expect(reconnectedSecretIds).not.toContain(secretId); - // And the operator has to choose access again — the app profile is not back. - expect(await db.select().from(toolProfiles).where(eq(toolProfiles.profileKey, `app:${connectionId}`))).toEqual([]); + // A fresh connect recreates the deny-by-default profile, but does not restore installs. + await expect(db.select().from(toolProfiles).where(eq(toolProfiles.profileKey, `app:${connectionId}`))) + .resolves.toHaveLength(1); expect(await service.listConnectionInstalls(connectionId, company.id)).toEqual([]); }); }); diff --git a/server/src/__tests__/tool-connection-visibility.test.ts b/server/src/__tests__/tool-connection-visibility.test.ts new file mode 100644 index 0000000000..6a6ea21f74 --- /dev/null +++ b/server/src/__tests__/tool-connection-visibility.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vitest"; +import { filterVisibleToolConnections } from "../routes/tool-access.js"; + +const connections = [ + { id: "other-draft", status: "draft", createdByUserId: "other-user" }, + { id: "own-draft", status: "draft", createdByUserId: "member-user" }, + { id: "active", status: "active", createdByUserId: "other-user" }, +]; + +describe("tool connection visibility", () => { + it("keeps foreign drafts out of a regular member's reusable connection candidates", () => { + expect(filterVisibleToolConnections(connections, { + userId: "member-user", + canManageConnections: false, + })).toEqual([ + connections[1], + connections[2], + ]); + }); + + it("keeps every draft visible to a connection manager", () => { + expect(filterVisibleToolConnections(connections, { + userId: "owner-user", + canManageConnections: true, + })).toEqual(connections); + }); +}); diff --git a/server/src/__tests__/tool-gateway-service.test.ts b/server/src/__tests__/tool-gateway-service.test.ts index 9aa3d1733c..509b8d2163 100644 --- a/server/src/__tests__/tool-gateway-service.test.ts +++ b/server/src/__tests__/tool-gateway-service.test.ts @@ -6,6 +6,7 @@ import { agents, approvals, companies, + connectionGrants, createDb, heartbeatRuns, issueApprovals, @@ -41,6 +42,7 @@ function createTestToolGatewayService(db: ReturnType, options: return createToolGatewayService(db, { ...options, toolActionSigningSecret: options.toolActionSigningSecret ?? testToolActionSigningSecret, + remoteHttpRequest: options.remoteHttpRequest ?? (async (url, init) => fetch(url, init)), }); } @@ -93,8 +95,17 @@ async function createRemoteMcpToolFixture(db: ReturnType, compa healthStatus: "ok", // Use a public IP literal so protocol tests remain independent of DNS while // still exercising the production egress guard and their global fetch stub. + credentialPolicy: "shared", config: { url: "https://8.8.8.8/mcp" }, }).returning().then((rows) => rows[0]!); + await db.insert(connectionGrants).values({ + companyId, + connectionId: connection.id, + kind: "organization", + credentialSecretRefs: [], + status: "active", + isDefault: true, + }); const catalogEntry = await db.insert(toolCatalogEntries).values({ companyId, applicationId: application.id, diff --git a/server/src/__tests__/tool-gateway.test.ts b/server/src/__tests__/tool-gateway.test.ts index 8a9b8c299d..c23340b216 100644 --- a/server/src/__tests__/tool-gateway.test.ts +++ b/server/src/__tests__/tool-gateway.test.ts @@ -12,6 +12,9 @@ import { companySecretVersions, companyMemberships, companies, + connectionGrantMembers, + connectionGrantDelegations, + connectionGrants, createDb, heartbeatRuns, issueThreadInteractions, @@ -113,6 +116,16 @@ async function createIssueAndRun(db: Db, companyId: string, agentId: string) { return { project, issue, run }; } +async function createActiveMember(db: Db, companyId: string, userId: string) { + await db.insert(companyMemberships).values({ + companyId, + principalType: "user", + principalId: userId, + status: "active", + membershipRole: "member", + }); +} + async function allowToolsForAgent(db: Db, companyId: string, agentId: string, toolNames: string[]) { const profile = await db .insert(toolProfiles) @@ -214,6 +227,14 @@ async function createRemoteMcpTool( credentialRefs: input.credentialRefs ?? [], credentialSecretRefs: input.credentialSecretRefs ?? [], }).returning(); + await db.insert(connectionGrants).values({ + companyId, + connectionId: connection.id, + kind: "organization", + credentialSecretRefs: connection.credentialSecretRefs, + status: "active", + isDefault: true, + }); if (input.credentialRefs?.length || input.credentialSecretRefs?.length) { await db.insert(companySecretBindings).values([ ...(input.credentialRefs ?? []).map((ref) => ({ @@ -276,6 +297,11 @@ async function createLocalStdioMcpTool( healthStatus?: "unknown" | "healthy" | "degraded" | "failed" | "unchecked" | "ok" | "error" | "missing_secret"; catalogStatus?: "active" | "disabled" | "quarantined" | "removed"; riskLevel?: "read" | "write" | "destructive"; + credentialPolicy?: "shared" | "per_user" | "per_user_with_fallback"; + credentialSecretRefs?: typeof toolConnections.$inferInsert["credentialSecretRefs"]; + stdioScript?: string; + envKeys?: string[]; + connectionConfig?: Record; } = {}, ) { const applicationKey = input.applicationKey ?? `local-app-${randomUUID().slice(0, 8)}`; @@ -333,9 +359,31 @@ rl.on("line", (line) => { status: input.connectionStatus ?? "active", enabled: input.connectionEnabled ?? true, healthStatus: input.healthStatus ?? "ok", + credentialPolicy: input.credentialPolicy ?? "shared", config: { templateId: templateKey, ...(input.connectionConfig ?? {}) }, transportConfig: { templateId: templateKey, ...(input.connectionConfig ?? {}) }, + credentialSecretRefs: input.credentialSecretRefs ?? [], }).returning(); + await db.insert(connectionGrants).values({ + companyId, + connectionId: connection.id, + kind: "organization", + credentialSecretRefs: connection.credentialSecretRefs, + status: "active", + isDefault: true, + }); + if (input.credentialSecretRefs?.length) { + await db.insert(companySecretBindings).values(input.credentialSecretRefs.map((ref) => ({ + companyId, + secretId: ref.secretId, + targetType: "tool_connection" as const, + targetId: connection.id, + configPath: ref.configPath, + versionSelector: String(ref.versionSelector ?? "latest"), + required: ref.required ?? true, + label: ref.label ?? null, + }))).onConflictDoNothing(); + } const [catalogEntry] = await db.insert(toolCatalogEntries).values({ companyId, applicationId: application!.id, @@ -1276,13 +1324,26 @@ describeEmbeddedPostgres("tool gateway acceptance", () => { const company = await createCompany(db); const agent = await createAgent(db, company.id); const { run } = await createIssueAndRun(db, company.id, agent.id); + const allowedToken = await secretService(db).create(company.id, { + name: `Local stdio token ${randomUUID()}`, + key: `local_stdio_token_${randomUUID().replace(/-/g, "")}`, + provider: "local_encrypted", + value: "allowed-token", + }); const localTool = await createLocalStdioMcpTool(db, company.id, { applicationKey: "local-env-demo", connectionName: "Local Env Demo", toolName: "inspect_env", title: "Inspect env", envKeys: ["ALLOWED_TOKEN"], - connectionConfig: { env: { ALLOWED_TOKEN: "allowed-token", EXTRA_CONFIG: "extra-value", NODE_OPTIONS: "--trace-warnings" } }, + credentialSecretRefs: [{ + secretId: allowedToken.id, + versionSelector: "latest", + configPath: "env.ALLOWED_TOKEN", + required: true, + label: "Allowed token", + }], + connectionConfig: { env: { ALLOWED_TOKEN: "connection-level-token", EXTRA_CONFIG: "extra-value", NODE_OPTIONS: "--trace-warnings" } }, stdioScript: ` const readline = require("node:readline"); const rl = readline.createInterface({ input: process.stdin }); @@ -1352,6 +1413,149 @@ rl.on("line", (line) => { } }); + it("passes only the selected grant identity to local stdio MCP processes", async () => { + const company = await createCompany(db); + const agent = await createAgent(db, company.id); + const { run } = await createIssueAndRun(db, company.id, agent.id); + await createActiveMember(db, company.id, "alice"); + await db.update(heartbeatRuns).set({ responsibleUserId: "alice" }).where(eq(heartbeatRuns.id, run.id)); + const values = { + organization: `organization-${randomUUID()}`, + alice: `alice-${randomUUID()}`, + bob: `bob-${randomUUID()}`, + }; + const organizationSecret = await secretService(db).create(company.id, { + name: `Organization stdio token ${randomUUID()}`, + key: `organization_stdio_${randomUUID().replace(/-/g, "")}`, + provider: "local_encrypted", + value: values.organization, + }); + const aliceSecret = await secretService(db).create(company.id, { + name: `Alice stdio token ${randomUUID()}`, + key: `alice_stdio_${randomUUID().replace(/-/g, "")}`, + provider: "local_encrypted", + value: values.alice, + }); + const bobSecret = await secretService(db).create(company.id, { + name: `Bob stdio token ${randomUUID()}`, + key: `bob_stdio_${randomUUID().replace(/-/g, "")}`, + provider: "local_encrypted", + value: values.bob, + }); + const localTool = await createLocalStdioMcpTool(db, company.id, { + applicationKey: "local-grant-identity", + toolName: "identity", + title: "Grant identity", + envKeys: ["IDENTITY_TOKEN"], + credentialSecretRefs: [{ + secretId: organizationSecret.id, + versionSelector: "latest", + configPath: "env.IDENTITY_TOKEN", + required: true, + label: "Organization identity", + }], + connectionConfig: { env: { IDENTITY_TOKEN: "legacy-connection-identity" } }, + stdioScript: ` +const readline = require("node:readline"); +const identities = ${JSON.stringify(values)}; +const rl = readline.createInterface({ input: process.stdin }); +rl.on("line", (line) => { + const message = JSON.parse(line); + if (message.method === "initialize") { + process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: message.id, result: { protocolVersion: "2024-11-05", capabilities: {}, serverInfo: { name: "identity-stdio", version: "0.0.0" } } }) + "\\n"); + return; + } + if (message.method === "tools/call") { + const identity = Object.entries(identities).find(([, value]) => value === process.env.IDENTITY_TOKEN)?.[0] ?? "unknown"; + process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: message.id, result: { content: [{ type: "text", text: identity }], structuredContent: { identity } } }) + "\\n"); + } +}); +`, + }); + const grantRef = (secretId: string, label: string) => ({ + secretId, + versionSelector: "latest" as const, + configPath: "env.IDENTITY_TOKEN", + required: true, + label, + }); + const [aliceGrant] = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: localTool.connection.id, + kind: "user", + subjectUserId: "alice", + credentialSecretRefs: [grantRef(aliceSecret.id, "Alice identity")], + status: "active", + isDefault: false, + }).returning(); + await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: localTool.connection.id, + kind: "user", + subjectUserId: "bob", + credentialSecretRefs: [grantRef(bobSecret.id, "Bob identity")], + status: "active", + isDefault: false, + }); + await allowAllToolsForAgent(db, company.id, agent.id); + const gateway = createTestToolGatewayService(db, { runtimeSupervisor: { idleTtlMs: 10_000 } }); + const session = await gateway.createSession({ companyId: company.id, agentId: agent.id, runId: run.id }); + const tool = (await gateway.listToolsForSession(session.token)).find((item) => item.connectionId === localTool.connection.id)!; + const executeIdentity = async () => { + const result = await gateway.executeTool({ sessionToken: session.token, tool: tool.name, parameters: {} }); + return (result.result as { data?: { structuredContent?: { identity?: string } } }).data?.structuredContent?.identity; + }; + + expect(await executeIdentity()).toBe("organization"); + await db.update(toolConnections).set({ credentialPolicy: "per_user" }).where(eq(toolConnections.id, localTool.connection.id)); + expect(await executeIdentity()).toBe("alice"); + await db.update(toolConnections).set({ credentialPolicy: "per_user_with_fallback" }).where(eq(toolConnections.id, localTool.connection.id)); + expect(await executeIdentity()).toBe("alice"); + await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, aliceGrant.id)); + expect(await executeIdentity()).toBe("organization"); + + await db.delete(toolRuntimeSlots).where(eq(toolRuntimeSlots.connectionId, localTool.connection.id)); + await db.update(toolConnections).set({ credentialPolicy: "per_user" }).where(eq(toolConnections.id, localTool.connection.id)); + await expect(gateway.executeTool({ sessionToken: session.token, tool: tool.name, parameters: {} })) + .rejects.toMatchObject({ status: 409, reasonCode: "user_authorization_required" }); + expect(await db.select().from(toolRuntimeSlots).where(eq(toolRuntimeSlots.connectionId, localTool.connection.id))).toHaveLength(0); + + const [organizationGrant] = await db.select().from(connectionGrants).where(and( + eq(connectionGrants.connectionId, localTool.connection.id), + eq(connectionGrants.kind, "organization"), + )); + await db.insert(connectionGrantMembers).values({ + companyId: company.id, + grantId: organizationGrant.id, + subjectType: "user", + subjectId: "sales-user", + }); + await db.update(toolConnections).set({ credentialPolicy: "shared" }).where(eq(toolConnections.id, localTool.connection.id)); + await expect(gateway.executeTool({ sessionToken: session.token, tool: tool.name, parameters: {} })) + .rejects.toMatchObject({ status: 403, reasonCode: "grant_audience_denied" }); + expect(await db.select().from(toolRuntimeSlots).where(eq(toolRuntimeSlots.connectionId, localTool.connection.id))).toHaveLength(0); + + await db.insert(connectionGrantMembers).values({ + companyId: company.id, + grantId: organizationGrant.id, + subjectType: "user", + subjectId: "alice", + }); + expect(await executeIdentity()).toBe("organization"); + + await db.delete(toolRuntimeSlots).where(eq(toolRuntimeSlots.connectionId, localTool.connection.id)); + await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, organizationGrant.id)); + await expect(gateway.executeTool({ sessionToken: session.token, tool: tool.name, parameters: {} })) + .rejects.toMatchObject({ status: 409, reasonCode: "organization_authorization_required" }); + + await db.delete(connectionGrantMembers).where(eq(connectionGrantMembers.grantId, organizationGrant.id)); + await db.update(toolConnections).set({ credentialPolicy: "per_user" }).where(eq(toolConnections.id, localTool.connection.id)); + await db.update(heartbeatRuns).set({ responsibleUserId: null }).where(eq(heartbeatRuns.id, run.id)); + await expect(gateway.executeTool({ sessionToken: session.token, tool: tool.name, parameters: {} })) + .rejects.toMatchObject({ status: 409, reasonCode: "user_authorization_required" }); + expect(await db.select().from(toolRuntimeSlots).where(eq(toolRuntimeSlots.connectionId, localTool.connection.id))).toHaveLength(0); + }); + it("keeps connected remote MCP gateway names collision-safe and excludes inactive catalog sources", async () => { const company = await createCompany(db); const agent = await createAgent(db, company.id); @@ -1629,6 +1833,152 @@ rl.on("line", (line) => { } }); + it("creates a personal authorization card and resumes after the user grant exists", async () => { + const company = await createCompany(db); + const agent = await createAgent(db, company.id); + const { issue, run } = await createIssueAndRun(db, company.id, agent.id); + await createActiveMember(db, company.id, "carol"); + await db.update(heartbeatRuns).set({ responsibleUserId: "carol" }).where(eq(heartbeatRuns.id, run.id)); + const fake = await startFakeRemoteMcpServer((fakeRequest) => ({ + body: { + jsonrpc: "2.0", + id: fakeRequest.body?.id, + result: { content: [{ type: "text", text: "connected" }] }, + }, + })); + try { + const { connection } = await createRemoteMcpTool(db, company.id, { + url: fake.url, + toolName: "whoami", + riskLevel: "read", + }); + await db.update(toolConnections).set({ credentialPolicy: "per_user" }).where(eq(toolConnections.id, connection.id)); + await allowAllToolsForAgent(db, company.id, agent.id); + const gateway = createTestToolGatewayService(db); + const session = await gateway.createSession({ companyId: company.id, agentId: agent.id, runId: run.id }); + const tool = (await gateway.listToolsForSession(session.token)).find((item) => item.providerType === "mcp_remote_http")!; + + await db.insert(issueThreadInteractions).values({ + companyId: company.id, + issueId: issue.id, + kind: "request_confirmation", + status: "pending", + continuationPolicy: "none", + requestedResolverPolicy: "anyone", + effectiveResolverPolicy: "anyone", + idempotencyKey: `connection-authorization:${connection.id}:carol`, + title: "Connect your account", + summary: `Connect ${connection.name} to continue`, + payload: { + version: 1, + prompt: `Connect your account to ${connection.name}`, + acceptLabel: "Open authorization", + rejectLabel: "Not now", + }, + }); + + await expect(gateway.executeTool({ sessionToken: session.token, tool: tool.name, parameters: {} })) + .rejects.toMatchObject({ status: 409, reasonCode: "user_authorization_required" }); + const [interaction] = await db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.issueId, issue.id)); + expect(interaction).toMatchObject({ + kind: "request_confirmation", + status: "pending", + continuationPolicy: "wake_assignee", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + addresseeUserId: "carol", + }); + expect(interaction!.payload).toMatchObject({ + prompt: `Connect your ${connection.name} account to continue`, + target: { key: `connection:${connection.uid}:user:carol` }, + }); + + await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: "carol", + credentialSecretRefs: [], + status: "active", + isDefault: false, + }); + const result = await gateway.executeTool({ sessionToken: session.token, tool: tool.name, parameters: {} }); + expect(result).toMatchObject({ status: "completed", result: { content: "connected" } }); + expect(fake.requests).toHaveLength(1); + } finally { + await fake.close(); + } + }); + + it("requires an explicit named-agent delegation for autonomous personal-identity runs", async () => { + const company = await createCompany(db); + const agent = await createAgent(db, company.id); + const { issue, run } = await createIssueAndRun(db, company.id, agent.id); + await createActiveMember(db, company.id, "alice"); + await db.update(heartbeatRuns).set({ + responsibleUserId: "alice", + invocationSource: "automation", + }).where(eq(heartbeatRuns.id, run.id)); + const fake = await startFakeRemoteMcpServer((fakeRequest) => ({ + body: { + jsonrpc: "2.0", + id: fakeRequest.body?.id, + result: { content: [{ type: "text", text: "delegated" }] }, + }, + })); + try { + const { connection } = await createRemoteMcpTool(db, company.id, { + url: fake.url, + toolName: "whoami", + riskLevel: "read", + }); + await db.update(toolConnections).set({ credentialPolicy: "per_user" }).where(eq(toolConnections.id, connection.id)); + const grant = await db.insert(connectionGrants).values({ + companyId: company.id, + connectionId: connection.id, + kind: "user", + subjectUserId: "alice", + credentialSecretRefs: [], + status: "active", + isDefault: false, + }).returning().then((rows) => rows[0]!); + await allowAllToolsForAgent(db, company.id, agent.id); + const gateway = createTestToolGatewayService(db); + const session = await gateway.createSession({ companyId: company.id, agentId: agent.id, runId: run.id }); + const tool = (await gateway.listToolsForSession(session.token)).find((item) => item.providerType === "mcp_remote_http")!; + + await expect(gateway.executeTool({ sessionToken: session.token, tool: tool.name, parameters: {} })) + .rejects.toMatchObject({ status: 409, reasonCode: "standing_delegation_required" }); + expect(fake.requests).toHaveLength(0); + expect(await db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.issueId, issue.id))) + .toEqual([expect.objectContaining({ + status: "pending", + addresseeUserId: "alice", + idempotencyKey: `connection-delegation:${connection.id}:alice:${agent.id}`, + })]); + + await db.insert(connectionGrantDelegations).values({ + companyId: company.id, + grantId: grant.id, + agentId: agent.id, + createdByUserId: "alice", + }); + await expect(gateway.executeTool({ sessionToken: session.token, tool: tool.name, parameters: {} })) + .resolves.toMatchObject({ status: "completed", result: { content: "delegated" } }); + expect(fake.requests).toHaveLength(1); + + await db.update(companyMemberships).set({ status: "suspended" }).where(and( + eq(companyMemberships.companyId, company.id), + eq(companyMemberships.principalId, "alice"), + )); + await expect(gateway.executeTool({ sessionToken: session.token, tool: tool.name, parameters: {} })) + .rejects.toMatchObject({ status: 403, reasonCode: "grant_owner_membership_inactive" }); + expect(fake.requests).toHaveLength(1); + } finally { + await fake.close(); + } + }); + it("keeps managed credentials authoritative even when legacy override flags are set", async () => { const company = await createCompany(db); const agent = await createAgent(db, company.id); diff --git a/server/src/__tests__/workspace-runtime.test.ts b/server/src/__tests__/workspace-runtime.test.ts index e829ceed65..fdbeff3012 100644 --- a/server/src/__tests__/workspace-runtime.test.ts +++ b/server/src/__tests__/workspace-runtime.test.ts @@ -6806,6 +6806,7 @@ describeEmbeddedPostgres("workspace runtime service control persistence", () => services: [{ name: "paperclip-dev", command, + env: { PAPERCLIP_PUBLIC_URL: "http://127.0.0.1:3100" }, port: { type: "fixed", value: 45_439, envKey: "PORT" }, readiness: { type: "http", @@ -7717,6 +7718,7 @@ describeEmbeddedPostgres("workspace runtime startup reconciliation", () => { { name: "paperclip-dev", command, + env: { PAPERCLIP_PUBLIC_URL: "http://127.0.0.1:3100" }, port: legacyPort, // The pre-feature block: backend URL only, no exposure declaration. expose: { type: "url", urlTemplate: "http://127.0.0.1:{{port}}" }, diff --git a/server/src/middleware/error-handler.ts b/server/src/middleware/error-handler.ts index 0c0a218a1d..885f9d773b 100644 --- a/server/src/middleware/error-handler.ts +++ b/server/src/middleware/error-handler.ts @@ -101,11 +101,15 @@ export function errorHandler( const workspaceRepairPreconditionFailure = details?.code === "workspace_repair_precondition_failed"; const structuredConnectionError = new Set([ "user_authorization_required", + "organization_authorization_required", + "grant_audience_denied", "grant_revoked", "needs_reauthorization", "installation_required", "connection_not_installed", "subject_not_permitted", + "standing_delegation_required", + "grant_owner_membership_inactive", ]).has(typeof details?.code === "string" ? details.code : ""); recordResponsibleUserDenialFromHttpError(req, details); if (err.status >= 500) { diff --git a/server/src/routes/access.ts b/server/src/routes/access.ts index 6c07bd63b8..46067b2910 100644 --- a/server/src/routes/access.ts +++ b/server/src/routes/access.ts @@ -14,7 +14,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { Router } from "express"; import type { Request } from "express"; -import { and, desc, eq, gt, inArray, isNotNull, isNull, lte, ne, sql } from "drizzle-orm"; +import { and, desc, eq, gt, inArray, isNotNull, isNull, lte, ne } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { assets, @@ -47,7 +47,7 @@ import { PERMISSION_KEYS, isUuidLike, } from "@paperclipai/shared"; -import type { DeploymentExposure, DeploymentMode, HumanCompanyMembershipRole, PermissionKey } from "@paperclipai/shared"; +import type { DeploymentExposure, DeploymentMode, HumanCompanyMembershipRole } from "@paperclipai/shared"; import { forbidden, conflict, @@ -121,11 +121,6 @@ const INVITE_TOKEN_MAX_RETRIES = 5; const COMPANY_INVITE_TTL_MS = 72 * 60 * 60 * 1000; const INVITE_RESOLUTION_DNS_TIMEOUT_MS = 3_000; -type MemberGrantPayload = { - permissionKey: PermissionKey; - scope?: Record | null; -}; - export function createInviteToken() { const suffix = randomBytes(INVITE_TOKEN_ENTROPY_BYTES).toString("base64url"); return `${INVITE_TOKEN_PREFIX}${suffix}`; @@ -4491,69 +4486,7 @@ export function accessRoutes( if (!memberToUpdate) throw notFound("Member not found"); await assertCanManageCompanyMember(req, access, companyId, memberToUpdate); - const updated = await db.transaction(async (tx) => { - await tx.execute(sql` - select ${companyMemberships.id} - from ${companyMemberships} - where ${companyMemberships.companyId} = ${companyId} - and ${companyMemberships.principalType} = 'user' - and ${companyMemberships.status} = 'active' - and ${companyMemberships.membershipRole} = 'owner' - for update - `); - - const existing = await tx - .select() - .from(companyMemberships) - .where( - and( - eq(companyMemberships.companyId, companyId), - eq(companyMemberships.id, memberId), - ), - ) - .then((rows) => rows[0] ?? null); - if (!existing) return null; - - const nextMembershipRole = - req.body.membershipRole !== undefined - ? req.body.membershipRole - : existing.membershipRole; - const nextStatus = req.body.status ?? existing.status; - - if ( - existing.principalType === "user" && - existing.status === "active" && - existing.membershipRole === "owner" && - (nextStatus !== "active" || nextMembershipRole !== "owner") - ) { - const activeOwnerCount = await tx - .select({ id: companyMemberships.id }) - .from(companyMemberships) - .where( - and( - eq(companyMemberships.companyId, companyId), - eq(companyMemberships.principalType, "user"), - eq(companyMemberships.status, "active"), - eq(companyMemberships.membershipRole, "owner"), - ), - ) - .then((rows) => rows.length); - if (activeOwnerCount <= 1) { - throw conflict("Cannot remove the last active owner"); - } - } - - return tx - .update(companyMemberships) - .set({ - membershipRole: nextMembershipRole, - status: nextStatus, - updatedAt: new Date(), - }) - .where(eq(companyMemberships.id, existing.id)) - .returning() - .then((rows) => rows[0] ?? existing); - }); + const updated = await access.updateMember(companyId, memberId, req.body); if (!updated) throw notFound("Member not found"); await logActivity(db, { @@ -4588,98 +4521,16 @@ export function accessRoutes( if (!memberToUpdate) throw notFound("Member not found"); await assertCanManageCompanyMember(req, access, companyId, memberToUpdate); - const updated = await db.transaction(async (tx) => { - await tx.execute(sql` - select ${companyMemberships.id} - from ${companyMemberships} - where ${companyMemberships.companyId} = ${companyId} - and ${companyMemberships.principalType} = 'user' - and ${companyMemberships.status} = 'active' - and ${companyMemberships.membershipRole} = 'owner' - for update - `); - - const existing = await tx - .select() - .from(companyMemberships) - .where( - and( - eq(companyMemberships.companyId, companyId), - eq(companyMemberships.id, memberId), - ), - ) - .then((rows) => rows[0] ?? null); - if (!existing) return null; - - const nextMembershipRole = - req.body.membershipRole !== undefined - ? req.body.membershipRole - : existing.membershipRole; - const nextStatus = req.body.status ?? existing.status; - - if ( - existing.principalType === "user" && - existing.status === "active" && - existing.membershipRole === "owner" && - (nextStatus !== "active" || nextMembershipRole !== "owner") - ) { - const activeOwnerCount = await tx - .select({ id: companyMemberships.id }) - .from(companyMemberships) - .where( - and( - eq(companyMemberships.companyId, companyId), - eq(companyMemberships.principalType, "user"), - eq(companyMemberships.status, "active"), - eq(companyMemberships.membershipRole, "owner"), - ), - ) - .then((rows) => rows.length); - if (activeOwnerCount <= 1) { - throw conflict("Cannot remove the last active owner"); - } - } - - const now = new Date(); - const updatedMember = await tx - .update(companyMemberships) - .set({ - membershipRole: nextMembershipRole, - status: nextStatus, - updatedAt: now, - }) - .where(eq(companyMemberships.id, existing.id)) - .returning() - .then((rows) => rows[0] ?? existing); - - await tx - .delete(principalPermissionGrants) - .where( - and( - eq(principalPermissionGrants.companyId, companyId), - eq(principalPermissionGrants.principalType, existing.principalType), - eq(principalPermissionGrants.principalId, existing.principalId), - ), - ); - - const grants = (req.body.grants ?? []) as MemberGrantPayload[]; - if (grants.length > 0) { - await tx.insert(principalPermissionGrants).values( - grants.map((grant) => ({ - companyId, - principalType: existing.principalType, - principalId: existing.principalId, - permissionKey: grant.permissionKey, - scope: grant.scope ?? null, - grantedByUserId: req.actor.userId ?? null, - createdAt: now, - updatedAt: now, - })), - ); - } - - return updatedMember; - }); + const updated = await access.updateMemberAndPermissions( + companyId, + memberId, + { + membershipRole: req.body.membershipRole, + status: req.body.status, + grants: req.body.grants ?? [], + }, + req.actor.userId ?? null, + ); if (!updated) throw notFound("Member not found"); await logActivity(db, { diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 90330486c9..5ee180e628 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -4333,6 +4333,7 @@ export function issueRoutes( effectiveResolverPolicy: string; resolverPolicyProvenance?: string | null; addresseeAgentId?: string | null; + addresseeUserId?: string | null; kind: string; status: string; payload?: unknown; @@ -11274,6 +11275,7 @@ export function issueRoutes( interactionStatus: interaction.status, continuationPolicy: interaction.continuationPolicy, addresseeAgentId: interaction.addresseeAgentId ?? null, + addresseeUserId: interaction.addresseeUserId ?? null, requestedResolverPolicy: interaction.requestedResolverPolicy, effectiveResolverPolicy: interaction.effectiveResolverPolicy, resolverPolicyProvenance: interaction.resolverPolicyProvenance, diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index 2407224284..d7180f70c6 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -199,6 +199,7 @@ import { createToolApplicationSchema, updateToolApplicationSchema, createToolConnectionSchema, + createConnectionGrantDelegationSchema, connectionTokenRequestSchema, startConnectionAuthorizationSchema, createToolStdioCommandTemplateSchema, @@ -915,6 +916,8 @@ const BOARD_ONLY_OPERATIONS = new Set([ "GET /api/tool-connections/{connectionId}", "GET /api/tool-connections/{connectionId}/grants", "POST /api/tool-connections/{connectionId}/grants/installations", + "POST /api/tool-connections/{connectionId}/grants/{grantId}/delegations", + "DELETE /api/tool-connections/{connectionId}/grants/{grantId}/delegations/{delegationId}", "DELETE /api/tool-connections/{connectionId}/grants/{grantId}", "GET /api/tool-connections/{connectionId}/usage", "PATCH /api/tool-connections/{connectionId}", @@ -7289,6 +7292,22 @@ registerCurrentRoute({ responses: { 201: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound }, }); +registerCurrentRoute({ + method: "post", + path: "/api/tool-connections/{connectionId}/grants/{grantId}/delegations", + tags: ["tool-access"], + summary: "Delegate a personal tool connection grant to an agent", + body: createConnectionGrantDelegationSchema, + responses: { 201: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound }, +}); + +registerCurrentRoute({ + method: "delete", + path: "/api/tool-connections/{connectionId}/grants/{grantId}/delegations/{delegationId}", + tags: ["tool-access"], + summary: "Revoke a personal tool connection grant delegation", +}); + registerCurrentRoute({ method: "delete", path: "/api/tool-connections/{connectionId}/grants/{grantId}", @@ -7303,6 +7322,13 @@ registerCurrentRoute({ summary: "Get tool connection usage", }); +registerCurrentRoute({ + method: "put", + path: "/api/tool-connections/{connectionId}/grants/{grantId}/members", + tags: ["tool-access"], + summary: "Replace the member audience of a tool connection grant", +}); + registerCurrentRoute({ method: "get", path: "/api/tool-connections/{connectionId}/installs", diff --git a/server/src/routes/tool-access.ts b/server/src/routes/tool-access.ts index 02d6252407..ac97092481 100644 --- a/server/src/routes/tool-access.ts +++ b/server/src/routes/tool-access.ts @@ -9,7 +9,9 @@ import { type DeploymentExposure, type DeploymentMode, type PermissionKey, + type ToolConnectionCreateCapabilities, connectToolAppSchema, + createConnectionGrantDelegationSchema, createToolStdioCommandTemplateSchema, createToolApplicationSchema, createToolConnectionSchema, @@ -23,6 +25,7 @@ import { duplicateToolProfileSchema, finishToolAppSchema, reconnectToolAppSchema, + replaceConnectionGrantMembersSchema, reviewToolProfileNewToolsSchema, createToolTrustRuleFromActionRequestSchema, importMcpJsonSchema, @@ -50,6 +53,9 @@ import { oauthClientIdMetadataDocument, } from "../services/tool-access.js"; +const COMPANY_INSTALL_DENIAL_REASON = + "Only someone who can configure this connection can choose this."; + /** Allowlist (e.g. Google Sheets allowed spreadsheet ids) lives in connection config. */ function allowlistIds(config: Record | null | undefined): string[] { const raw = config?.allowedSpreadsheetIds; @@ -104,6 +110,19 @@ function classifyConnectionUpdate( return events; } +export function filterVisibleToolConnections( + connections: T[], + actor: { userId?: string | null; canManageConnections: boolean }, +): T[] { + return connections.filter((connection) => + connection.status !== "draft" + || actor.canManageConnections + || Boolean(actor.userId && connection.createdByUserId === actor.userId)); +} + export function toolAccessRoutes( db: Db, options: { @@ -227,10 +246,10 @@ export function toolAccessRoutes( eq(connectionGrants.connectionId, connection.id), eq(connectionGrants.status, "active"), or( - eq(connectionGrants.kind, "workspace"), + eq(connectionGrants.kind, "organization"), req.actor.userId ? and(eq(connectionGrants.kind, "user"), eq(connectionGrants.subjectUserId, req.actor.userId)) - : eq(connectionGrants.kind, "workspace"), + : eq(connectionGrants.kind, "organization"), ), )) .limit(1); @@ -238,6 +257,118 @@ export function toolAccessRoutes( throw forbidden("You need access to this connection before you can install it on an agent"); } + /** + * Non-throwing membership probe for capability reporting (PAP-17835). + * + * `activeToolMembership` throws for viewers because it guards mutations. The + * personal-connections UI still has to *render* for a viewer, so capability + * computation needs the role without the 403. Returns `null` for a principal + * with no scoped membership (local implicit / instance admin), matching + * `activeToolMembership`'s "unrestricted" sentinel. + */ + function toolMembershipRole(req: Request, companyId: string): { + unrestricted: boolean; + role: string | null; + isViewer: boolean; + isActive: boolean; + } { + if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin) { + return { unrestricted: true, role: null, isViewer: false, isActive: true }; + } + const membership = Array.isArray(req.actor.memberships) + ? req.actor.memberships.find((item) => item.companyId === companyId) + : null; + const isActive = Boolean(membership && membership.status === "active"); + const role = membership?.membershipRole ?? null; + return { unrestricted: false, role, isViewer: role === "viewer", isActive }; + } + + async function isToolConnectionManagerQuiet(req: Request, companyId: string) { + const membership = toolMembershipRole(req, companyId); + if (membership.unrestricted) return true; + if (!membership.isActive || membership.isViewer) return false; + if (membership.role === "owner" || membership.role === "admin") return true; + return Boolean(req.actor.userId && await access.hasPermission( + companyId, + "user", + req.actor.userId, + "tools:manage_connections", + )); + } + + async function describeConnectionCreateCapabilities( + req: Request, + companyId: string, + ): Promise { + const canSetCompanyInstall = await isToolConnectionManagerQuiet(req, companyId); + return { + canSetCompanyInstall, + companyInstallReason: canSetCompanyInstall ? null : COMPANY_INSTALL_DENIAL_REASON, + }; + } + + /** + * Server-computed capabilities for the connection identity/install surfaces. + * The UI renders the §3 matrix from these booleans instead of reconstructing + * policy from role strings, because creator identity and per-agent edit rights + * are not derivable client-side. + */ + async function describeConnectionCapabilities( + req: Request, + connection: { id: string; companyId: string; createdByUserId?: string | null }, + ) { + const membership = toolMembershipRole(req, connection.companyId); + const isManager = await isToolConnectionManagerQuiet(req, connection.companyId); + const mutationCapable = membership.unrestricted || (membership.isActive && !membership.isViewer); + const isCreator = Boolean(req.actor.userId && connection.createdByUserId === req.actor.userId); + const canConfigure = isManager || (mutationCapable && isCreator); + const editableAgentIds: string[] = []; + if (mutationCapable) { + const companyAgents = await db + .select({ id: agents.id }) + .from(agents) + .where(eq(agents.companyId, connection.companyId)); + for (const agent of companyAgents) { + const decision = await access.decide({ + actor: req.actor, + action: "agent_config:update", + resource: { type: "agent", companyId: connection.companyId, agentId: agent.id }, + }); + if (decision.allowed) editableAgentIds.push(agent.id); + } + } + return { + canConfigure, + canCreateOrganizationGrant: canConfigure, + canSetCompanyInstall: canConfigure, + // Personal consent belongs to the person: it needs a named board user, and + // it is never available to a viewer or to an unauthenticated principal. + canConnectAsCurrentUser: Boolean(req.actor.userId) && mutationCapable, + canManageAgentInstalls: mutationCapable && editableAgentIds.length > 0, + canViewOtherPersonalIdentities: isManager, + editableAgentIds, + }; + } + + /** + * Per-grant authorization. Revoking your own identity is always allowed (the + * consent is yours to withdraw); revoking anyone else's is manager-only — that + * is the kill switch. Audience editing is creator-or-manager, and only for an + * organization grant. + */ + function describeGrantCapabilities( + grant: { kind: string; subjectUserId: string | null; createdByUserId: string | null }, + context: { userId: string | null; isManager: boolean; mutationCapable: boolean }, + ) { + if (!context.mutationCapable) return { canRevoke: false, canEditAudience: false }; + const isOwnGrant = Boolean(context.userId && grant.subjectUserId === context.userId); + const isGrantCreator = Boolean(context.userId && grant.createdByUserId === context.userId); + return { + canRevoke: isOwnGrant || isGrantCreator || context.isManager, + canEditAudience: grant.kind === "organization" && (isGrantCreator || context.isManager), + }; + } + async function assertBoardAnyToolPermission(req: Request, companyId: string, permissionKeys: PermissionKey[]) { assertBoard(req); assertCompanyAccess(req, companyId); @@ -341,6 +472,7 @@ export function toolAccessRoutes( assertCompanyAccess(req, companyId); const googleSheetsAvailability = googleSheetsRobotEmailFromEnv(); res.json({ + capabilities: await describeConnectionCreateCapabilities(req, companyId), apps: CONNECTABLE_APP_DEFINITIONS.map((app) => app.slug === "google-sheets" ? { @@ -378,9 +510,16 @@ export function toolAccessRoutes( const result = await svc.connectGalleryApp(companyId, req.body, getActorInfo(req)); if (result.auth?.kind === "oauth") { try { + // "Just me" must consent as the caller, not as the workspace: passing + // `subjectUserId` is what makes the callback land the tokens on the + // caller's personal grant instead of an organization grant + // (PAP-17835). The service refuses any subject other than the actor, + // so this cannot start consent on someone else's behalf. + const personalSubjectUserId = req.body.grantKind === "user" ? req.actor.userId ?? null : null; const start = await svc.startOAuth(companyId, result.connectionId, { redirectUri: oauthRedirectUri(), actor: getActorInfo(req), + ...(personalSubjectUserId ? { subjectUserId: personalSubjectUserId } : {}), }); result.auth.startUrl = start.authorizationUrl; result.auth.issuer = start.issuer ?? result.auth.issuer ?? null; @@ -692,7 +831,14 @@ export function toolAccessRoutes( assertBoard(req); const companyId = req.params.companyId as string; assertCompanyAccess(req, companyId); - res.json({ connections: await svc.listConnections(companyId) }); + const connections = await svc.listConnections(companyId); + const canManageConnections = await isToolConnectionManagerQuiet(req, companyId); + res.json({ + connections: filterVisibleToolConnections(connections, { + userId: req.actor.userId, + canManageConnections, + }), + }); }); router.post("/companies/:companyId/tools/connections", validate(createToolConnectionSchema), async (req, res) => { @@ -731,9 +877,70 @@ export function toolAccessRoutes( assertBoard(req); const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), "Tool connection not found"); if (!connection) return; - res.json(await svc.listConnectionGrants(connection.id, connection.companyId)); + const listed = await svc.listConnectionGrants(connection.id, connection.companyId); + const capabilities = await describeConnectionCapabilities(req, connection); + const membership = toolMembershipRole(req, connection.companyId); + const grantContext = { + userId: req.actor.userId ?? null, + isManager: await isToolConnectionManagerQuiet(req, connection.companyId), + mutationCapable: membership.unrestricted || (membership.isActive && !membership.isViewer), + }; + // A regular member has no reason to browse coworkers' personal identities; + // the manager kill switch does. Filtering here rather than in the UI keeps + // the list itself — not just its controls — under server policy. + const visibleGrants = listed.grants.filter((grant) => + grant.kind === "organization" + || capabilities.canViewOtherPersonalIdentities + || (grantContext.userId !== null && grant.subjectUserId === grantContext.userId)); + res.json({ + ...listed, + grants: visibleGrants.map((grant) => ({ + ...grant, + capabilities: describeGrantCapabilities(grant, grantContext), + })), + capabilities, + currentUserId: grantContext.userId, + members: capabilities.canConfigure + ? await svc.listConnectionAudienceMembers(connection.companyId) + : [], + }); }); + router.put( + "/tool-connections/:connectionId/grants/:grantId/members", + validate(replaceConnectionGrantMembersSchema), + async (req, res) => { + assertBoard(req); + const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), "Tool connection not found"); + if (!connection) return; + // Viewer/inactive principals are rejected before anything else, so a + // read-only member can never reach the audience writer. + activeToolMembership(req, connection.companyId); + const { grants } = await svc.listConnectionGrants(connection.id, connection.companyId); + const target = grants.find((grant) => grant.id === req.params.grantId); + if (!target) throw notFound("Connection grant not found"); + const isGrantCreator = Boolean(req.actor.userId && target.createdByUserId === req.actor.userId); + if (!isGrantCreator) await assertToolConnectionConfigureAccess(req, connection); + const memberUserIds = req.body.memberUserIds as string[]; + const grant = await svc.replaceConnectionGrantMembers( + connection.id, + target.id, + memberUserIds, + getActorInfo(req), + ); + await logActivity(db, { + companyId: connection.companyId, + actorType: "user", + actorId: req.actor.userId ?? "board", + action: "tool_connection.grant_audience_replaced", + entityType: "connection_grant", + entityId: grant.id, + details: { connectionId: connection.id, memberCount: memberUserIds.length }, + }); + res.json(grant); + }, + ); + router.post("/tool-connections/:connectionId/grants/installations", async (req, res) => { assertBoard(req); const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), "Tool connection not found"); @@ -786,6 +993,60 @@ export function toolAccessRoutes( res.json(grant); }); + router.post("/tool-connections/:connectionId/grants/:grantId/delegations", validate(createConnectionGrantDelegationSchema), async (req, res) => { + assertBoard(req); + const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), "Tool connection not found"); + if (!connection) return; + const ownerUserId = req.actor.userId; + if (!ownerUserId) throw forbidden("A named user is required to delegate a personal grant"); + const agentId = req.body.agentId; + const delegation = await svc.createConnectionGrantDelegation( + connection.id, + req.params.grantId as string, + agentId, + ownerUserId, + ); + await logActivity(db, { + companyId: connection.companyId, + actorType: "user", + actorId: ownerUserId, + action: "tool_connection.grant_delegated", + entityType: "connection_grant", + entityId: req.params.grantId as string, + details: { connectionId: connection.id, delegationId: delegation.id, agentId }, + }); + res.status(201).json(delegation); + }); + + router.delete("/tool-connections/:connectionId/grants/:grantId/delegations/:delegationId", async (req, res) => { + assertBoard(req); + const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), "Tool connection not found"); + if (!connection) return; + const { grants } = await svc.listConnectionGrants(connection.id, connection.companyId); + const grant = grants.find((candidate) => candidate.id === req.params.grantId); + if (!grant) throw notFound("Connection grant not found"); + const canRevokeOwnDelegation = Boolean(req.actor.userId && grant.subjectUserId === req.actor.userId); + if (!canRevokeOwnDelegation && !await isToolConnectionManager(req, connection.companyId)) { + throw forbidden("Only the personal grant owner or a connection manager can revoke a delegation"); + } + const delegation = await svc.revokeConnectionGrantDelegation( + connection.id, + grant.id, + req.params.delegationId as string, + getActorInfo(req), + ); + await logActivity(db, { + companyId: connection.companyId, + actorType: "user", + actorId: req.actor.userId ?? "board", + action: "tool_connection.grant_delegation_revoked", + entityType: "connection_grant", + entityId: grant.id, + details: { connectionId: connection.id, delegationId: delegation.id, agentId: delegation.agentId }, + }); + res.json(delegation); + }); + router.get("/tool-connections/:connectionId/usage", async (req, res) => { assertBoard(req); const connection = await getAccessibleResource(req, res, svc.getConnection(req.params.connectionId as string), "Tool connection not found"); diff --git a/server/src/services/access.ts b/server/src/services/access.ts index 1aaa53713c..73eaf0d49d 100644 --- a/server/src/services/access.ts +++ b/server/src/services/access.ts @@ -1,10 +1,18 @@ -import { and, eq, inArray, ne, sql } from "drizzle-orm"; +import { and, eq, inArray, ne, notInArray, sql } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { companyMemberships, + companySecretBindings, + companySecrets, + connectionGrantDelegations, + connectionGrantMembers, + connectionGrants, instanceUserRoles, issues, principalPermissionGrants, + toolAccessAuditEvents, + toolConnections, + userSecretDeclarations, } from "@paperclipai/db"; import type { PermissionKey, PrincipalType } from "@paperclipai/shared"; import { conflict } from "../errors.js"; @@ -28,6 +36,318 @@ type MemberArchiveInput = { export function accessService(db: Db) { const authorization = authorizationService(db); + async function sweepMemberConnectionAccess( + tx: Parameters[0]>[0], + companyId: string, + userId: string, + now: Date, + ) { + const departingAudienceRows = await tx.select({ + grantId: connectionGrantMembers.grantId, + }).from(connectionGrantMembers).where(and( + eq(connectionGrantMembers.companyId, companyId), + eq(connectionGrantMembers.subjectType, "user"), + eq(connectionGrantMembers.subjectId, userId), + )); + const departingAudienceGrantIds = [...new Set(departingAudienceRows.map((row) => row.grantId))]; + const affectedAudienceRows = departingAudienceGrantIds.length === 0 ? [] : await tx.select({ + grantId: connectionGrantMembers.grantId, + subjectId: connectionGrantMembers.subjectId, + }).from(connectionGrantMembers).where(and( + eq(connectionGrantMembers.companyId, companyId), + eq(connectionGrantMembers.subjectType, "user"), + inArray(connectionGrantMembers.grantId, departingAudienceGrantIds), + )); + const activeOrganizationAudienceGrants = departingAudienceGrantIds.length === 0 ? [] : await tx.select({ + id: connectionGrants.id, + }).from(connectionGrants).where(and( + eq(connectionGrants.companyId, companyId), + eq(connectionGrants.kind, "organization"), + eq(connectionGrants.status, "active"), + inArray(connectionGrants.id, departingAudienceGrantIds), + )); + const activeOrganizationAudienceGrantIds = new Set(activeOrganizationAudienceGrants.map((grant) => grant.id)); + const soleAudienceGrantIds = new Set(departingAudienceGrantIds.filter((grantId) => + activeOrganizationAudienceGrantIds.has(grantId) + && affectedAudienceRows.filter((row) => row.grantId === grantId).length === 1, + )); + + const ownedGrants = await tx.select({ + id: connectionGrants.id, + connectionId: connectionGrants.connectionId, + credentialSecretRefs: connectionGrants.credentialSecretRefs, + }).from(connectionGrants).where(and( + eq(connectionGrants.companyId, companyId), + eq(connectionGrants.kind, "user"), + eq(connectionGrants.subjectUserId, userId), + )); + const grantIds = ownedGrants.map((grant) => grant.id); + const ownedGrantIds = new Set(grantIds); + const affectedConnectionIds = [...new Set(ownedGrants.map((grant) => grant.connectionId))]; + const affectedConnections = new Set(affectedConnectionIds); + // Membership removal revokes personal connection identities. It must not + // erase unrelated user-scoped values used by agent or environment secret + // declarations or bindings. Start with only secrets the departing user's + // grants explicitly reference, then fail toward retention whenever another + // consumer still names the secret or its user-secret definition. + const referencedSecretIds = [...new Set(ownedGrants.flatMap((grant) => + grant.credentialSecretRefs.map((ref) => ref.secretId), + ))]; + const ownedSecrets = referencedSecretIds.length === 0 ? [] : await tx.select({ + id: companySecrets.id, + userSecretDefinitionId: companySecrets.userSecretDefinitionId, + }).from(companySecrets).where(and( + eq(companySecrets.companyId, companyId), + eq(companySecrets.scope, "user"), + eq(companySecrets.ownerUserId, userId), + inArray(companySecrets.id, referencedSecretIds), + )); + const ownedSecretIds = ownedSecrets.map((secret) => secret.id); + const ownedSecretSet = new Set(ownedSecretIds); + const retainedSecretIds = new Set(); + const sharedConnectionSecretIds = new Set(); + let grantMemberRefs: Array<{ grantId: string; subjectId: string }> = []; + let existingMemberUserIds = new Set(); + let grantRefs: Array<{ + id: string; + connectionId: string; + kind: typeof connectionGrants.$inferSelect.kind; + status: typeof connectionGrants.$inferSelect.status; + credentialSecretRefs: typeof connectionGrants.$inferSelect.credentialSecretRefs; + }> = []; + let connectionRefs: Array<{ + id: string; + credentialRefs: typeof toolConnections.$inferSelect.credentialRefs; + credentialSecretRefs: typeof toolConnections.$inferSelect.credentialSecretRefs; + }> = []; + if (ownedSecretIds.length > 0) { + const definitionIds = ownedSecrets.flatMap((secret) => + secret.userSecretDefinitionId ? [secret.userSecretDefinitionId] : [], + ); + const [ + bindingRefs, + declarationRefs, + allGrantRefs, + allConnectionRefs, + allGrantMemberRefs, + membershipRefs, + ] = await Promise.all([ + tx.select({ + secretId: companySecretBindings.secretId, + targetType: companySecretBindings.targetType, + targetId: companySecretBindings.targetId, + }).from(companySecretBindings).where(and( + eq(companySecretBindings.companyId, companyId), + inArray(companySecretBindings.secretId, ownedSecretIds), + )), + definitionIds.length === 0 ? Promise.resolve([]) : tx.select({ + userSecretDefinitionId: userSecretDeclarations.userSecretDefinitionId, + }).from(userSecretDeclarations).where(and( + eq(userSecretDeclarations.companyId, companyId), + inArray(userSecretDeclarations.userSecretDefinitionId, definitionIds), + )), + tx.select({ + id: connectionGrants.id, + connectionId: connectionGrants.connectionId, + kind: connectionGrants.kind, + status: connectionGrants.status, + credentialSecretRefs: connectionGrants.credentialSecretRefs, + }).from(connectionGrants).where(eq(connectionGrants.companyId, companyId)), + tx.select({ + id: toolConnections.id, + credentialRefs: toolConnections.credentialRefs, + credentialSecretRefs: toolConnections.credentialSecretRefs, + }).from(toolConnections).where(eq(toolConnections.companyId, companyId)), + tx.select({ + grantId: connectionGrantMembers.grantId, + subjectId: connectionGrantMembers.subjectId, + }).from(connectionGrantMembers).where(and( + eq(connectionGrantMembers.companyId, companyId), + eq(connectionGrantMembers.subjectType, "user"), + )), + tx.select({ userId: companyMemberships.principalId }).from(companyMemberships).where(and( + eq(companyMemberships.companyId, companyId), + eq(companyMemberships.principalType, "user"), + ne(companyMemberships.principalId, userId), + )), + ]); + grantRefs = allGrantRefs; + connectionRefs = allConnectionRefs; + grantMemberRefs = allGrantMemberRefs; + existingMemberUserIds = new Set(membershipRefs.map((row) => row.userId)); + + for (const binding of bindingRefs) { + if (binding.targetType !== "tool_connection" || !affectedConnections.has(binding.targetId)) { + retainedSecretIds.add(binding.secretId); + } + } + const declaredDefinitions = new Set(declarationRefs.map((row) => row.userSecretDefinitionId)); + for (const secret of ownedSecrets) { + if (secret.userSecretDefinitionId && declaredDefinitions.has(secret.userSecretDefinitionId)) { + retainedSecretIds.add(secret.id); + } + } + for (const grant of grantRefs) { + if (ownedGrantIds.has(grant.id)) continue; + const grantAudience = grantMemberRefs.filter((member) => member.grantId === grant.id); + const remainingGrantAudience = grantAudience.filter((member) => member.subjectId !== userId); + const hasSurvivingOrganizationAudience = grant.kind === "organization" + && grant.status === "active" + && ( + soleAudienceGrantIds.has(grant.id) + ? true + : remainingGrantAudience.length === 0 + ? existingMemberUserIds.size > 0 + : remainingGrantAudience.some((member) => existingMemberUserIds.has(member.subjectId)) + ); + for (const ref of grant.credentialSecretRefs) { + if (!ownedSecretSet.has(ref.secretId)) continue; + if (!affectedConnections.has(grant.connectionId)) { + retainedSecretIds.add(ref.secretId); + } else if (grant.kind === "user" || hasSurvivingOrganizationAudience) { + // A connection may temporarily carry separate user grants that + // reference the same credential, or an organization grant may + // still have another persisted audience member. A sole named + // audience row stays persisted instead of being widened to company + // scope; both resolvers require current active membership, so the + // row remains dormant until company access is restored. Pending, + // suspended, and archived memberships are intentionally included + // because company access can reactivate each of them later. + retainedSecretIds.add(ref.secretId); + sharedConnectionSecretIds.add(ref.secretId); + } + } + } + for (const connection of connectionRefs) { + if (affectedConnections.has(connection.id)) continue; + for (const ref of [...connection.credentialRefs, ...connection.credentialSecretRefs]) { + if (ownedSecretSet.has(ref.secretId)) retainedSecretIds.add(ref.secretId); + } + } + } + const secretIdsToDelete = ownedSecretIds.filter((secretId) => !retainedSecretIds.has(secretId)); + const connectionSecretIdsToRemove = new Set( + ownedSecretIds.filter((secretId) => !sharedConnectionSecretIds.has(secretId)), + ); + const removedDelegations = grantIds.length === 0 ? [] : await tx + .delete(connectionGrantDelegations) + .where(and( + eq(connectionGrantDelegations.companyId, companyId), + inArray(connectionGrantDelegations.grantId, grantIds), + )) + .returning(); + if (grantIds.length > 0) { + await tx.update(connectionGrants).set({ + status: "revoked", + isDefault: false, + revokedAt: now, + revokedByUserId: null, + revokedByAgentId: null, + updatedAt: now, + }).where(and( + eq(connectionGrants.companyId, companyId), + inArray(connectionGrants.id, grantIds), + )); + } + if (ownedSecretIds.length > 0) { + for (const grant of grantRefs) { + if (!ownedGrantIds.has(grant.id) && !affectedConnections.has(grant.connectionId)) continue; + const refsToRemove = ownedGrantIds.has(grant.id) ? ownedSecretSet : connectionSecretIdsToRemove; + const credentialSecretRefs = grant.credentialSecretRefs.filter( + (ref) => !refsToRemove.has(ref.secretId), + ); + if (credentialSecretRefs.length !== grant.credentialSecretRefs.length) { + await tx.update(connectionGrants).set({ + credentialSecretRefs, + ...(ownedGrantIds.has(grant.id) || grant.status === "revoked" ? { + status: "revoked" as const, + isDefault: false, + } : { + status: "needs_reauthorization" as const, + isDefault: false, + }), + updatedAt: now, + }) + .where(eq(connectionGrants.id, grant.id)); + } + } + for (const connection of connectionRefs) { + if (!affectedConnections.has(connection.id)) continue; + const credentialRefs = connection.credentialRefs.filter( + (ref) => !connectionSecretIdsToRemove.has(ref.secretId), + ); + const credentialSecretRefs = connection.credentialSecretRefs.filter( + (ref) => !connectionSecretIdsToRemove.has(ref.secretId), + ); + if ( + credentialRefs.length !== connection.credentialRefs.length || + credentialSecretRefs.length !== connection.credentialSecretRefs.length + ) { + const hasUnaffectedActiveGrant = grantRefs.some((grant) => + grant.connectionId === connection.id + && !ownedGrantIds.has(grant.id) + && grant.status === "active" + && grant.credentialSecretRefs.length > 0 + && grant.credentialSecretRefs.every((ref) => !connectionSecretIdsToRemove.has(ref.secretId)), + ); + await tx.update(toolConnections).set({ + credentialRefs, + credentialSecretRefs, + ...(!hasUnaffectedActiveGrant ? { + status: "draft" as const, + enabled: false, + healthStatus: "missing_secret" as const, + healthMessage: "Personal credential owner no longer has company access. Reauthorize this connection.", + lastError: "oauth_reauthorization_required", + } : {}), + updatedAt: now, + }) + .where(eq(toolConnections.id, connection.id)); + } + } + if (affectedConnectionIds.length > 0 && connectionSecretIdsToRemove.size > 0) { + await tx.delete(companySecretBindings).where(and( + eq(companySecretBindings.companyId, companyId), + eq(companySecretBindings.targetType, "tool_connection"), + inArray(companySecretBindings.targetId, affectedConnectionIds), + inArray(companySecretBindings.secretId, [...connectionSecretIdsToRemove]), + )); + } + if (secretIdsToDelete.length > 0) { + await tx.delete(companySecrets).where(and( + eq(companySecrets.companyId, companyId), + inArray(companySecrets.id, secretIdsToDelete), + )); + } + } + await tx.delete(connectionGrantMembers).where(and( + eq(connectionGrantMembers.companyId, companyId), + eq(connectionGrantMembers.subjectType, "user"), + eq(connectionGrantMembers.subjectId, userId), + soleAudienceGrantIds.size > 0 + ? notInArray(connectionGrantMembers.grantId, [...soleAudienceGrantIds]) + : undefined, + )); + if (removedDelegations.length > 0) { + const connectionByGrant = new Map(ownedGrants.map((grant) => [grant.id, grant.connectionId])); + await tx.insert(toolAccessAuditEvents).values(removedDelegations.map((delegation) => ({ + companyId, + connectionId: connectionByGrant.get(delegation.grantId) ?? null, + actorType: "system", + actorId: null, + action: "connection_grant.delegation_revoked", + outcome: "success", + reasonCode: "membership_removed", + details: { + grantId: delegation.grantId, + delegationId: delegation.id, + agentId: delegation.agentId, + ownerUserId: userId, + }, + }))); + } + } + async function isInstanceAdmin(userId: string | null | undefined): Promise { if (!userId) return false; const row = await db @@ -185,6 +505,7 @@ export function accessService(db: Db) { .select() .from(companyMemberships) .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.id, memberId))) + .for("update") .then((rows) => rows[0] ?? null); if (!existing) return null; @@ -216,6 +537,9 @@ export function accessService(db: Db) { } const now = new Date(); + if (existing.status === "active" && nextStatus !== "active" && existing.principalType === "user") { + await sweepMemberConnectionAccess(tx, companyId, existing.principalId, now); + } const updated = await tx .update(companyMemberships) .set({ @@ -334,6 +658,7 @@ export function accessService(db: Db) { .select() .from(companyMemberships) .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.id, memberId))) + .for("update") .then((rows) => rows[0] ?? null); if (!existing) return null; if (existing.principalType !== "user") { @@ -356,6 +681,7 @@ export function accessService(db: Db) { await assertAssignableArchiveTarget(companyId, input.reassignment, tx); const now = new Date(); + await sweepMemberConnectionAccess(tx, companyId, existing.principalId, now); const assignmentPatch = { assigneeAgentId: input.reassignment?.assigneeAgentId ?? null, assigneeUserId: input.reassignment?.assigneeUserId ?? null, @@ -449,11 +775,17 @@ export function accessService(db: Db) { companyIds: string[], options: { actorUserId?: string | null } = {}, ) { - const existing = await listUserCompanyAccess(userId); - const existingByCompany = new Map(existing.map((row) => [row.companyId, row])); const target = new Set(companyIds); await db.transaction(async (tx) => { + // Serialize every company-access removal/reactivation with personal OAuth + // completion, which locks the same membership row before writing secrets. + const existing = await tx + .select() + .from(companyMemberships) + .where(and(eq(companyMemberships.principalType, "user"), eq(companyMemberships.principalId, userId))) + .for("update"); + const existingByCompany = new Map(existing.map((row) => [row.companyId, row])); const toArchive = existing.filter((row) => !target.has(row.companyId) && row.status !== "archived"); if (toArchive.length > 0 && options.actorUserId && options.actorUserId === userId) { throw conflict("You cannot remove yourself"); @@ -489,9 +821,13 @@ export function accessService(db: Db) { } } if (toArchive.length > 0) { + const now = new Date(); + for (const membership of toArchive) { + await sweepMemberConnectionAccess(tx, membership.companyId, membership.principalId, now); + } await tx .update(companyMemberships) - .set({ status: "archived", updatedAt: new Date() }) + .set({ status: "archived", updatedAt: now }) .where(inArray(companyMemberships.id, toArchive.map((row) => row.id))); await tx .delete(principalPermissionGrants) @@ -736,6 +1072,7 @@ export function accessService(db: Db) { .select() .from(companyMemberships) .where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.id, memberId))) + .for("update") .then((rows) => rows[0] ?? null); if (!existing) return null; @@ -766,12 +1103,21 @@ export function accessService(db: Db) { } } + const now = new Date(); + if ( + existing.principalType === "user" && + existing.status !== "suspended" && + nextStatus === "suspended" + ) { + await sweepMemberConnectionAccess(tx, companyId, existing.principalId, now); + } + return tx .update(companyMemberships) .set({ membershipRole: nextMembershipRole, status: nextStatus, - updatedAt: new Date(), + updatedAt: now, }) .where(eq(companyMemberships.id, existing.id)) .returning() diff --git a/server/src/services/attention-resolver-audience.test.ts b/server/src/services/attention-resolver-audience.test.ts index 9a84661499..c2e3b7482d 100644 --- a/server/src/services/attention-resolver-audience.test.ts +++ b/server/src/services/attention-resolver-audience.test.ts @@ -28,6 +28,7 @@ describe("attention feed resolver audience", () => { effectiveResolverPolicySource: "requested", resolverPolicyProvenance: "inherited", addresseeAgentId: null, + addresseeUserId: null, addresseeName: null, createdByAgentId: "agent-watchdog", createdByAgentName: "Watchdog", diff --git a/server/src/services/attention.ts b/server/src/services/attention.ts index f78347aadc..a4cd945783 100644 --- a/server/src/services/attention.ts +++ b/server/src/services/attention.ts @@ -733,6 +733,7 @@ function interactionVerbs(kind: string, payload: Record) { export function interactionResolverAudience( row: { addresseeAgentId: string | null; + addresseeUserId?: string | null; createdByAgentId: string | null; requestedResolverPolicy: string; effectiveResolverPolicy: string; @@ -752,6 +753,7 @@ export function interactionResolverAudience( (row.effectiveResolverPolicySource ?? "requested") as IssueThreadInteractionEffectiveResolverPolicySource, resolverPolicyProvenance: provenance, addresseeAgentId: row.addresseeAgentId, + addresseeUserId: row.addresseeUserId ?? null, addresseeName: row.addresseeAgentId ? agentName(row.addresseeAgentId) : null, createdByAgentId: row.createdByAgentId, createdByAgentName: row.createdByAgentId ? agentName(row.createdByAgentId) : null, @@ -1172,6 +1174,7 @@ export function attentionService(db: Db, serviceOptions: AttentionServiceOptions summary: issueThreadInteractions.summary, payload: issueThreadInteractions.payload, addresseeAgentId: issueThreadInteractions.addresseeAgentId, + addresseeUserId: issueThreadInteractions.addresseeUserId, createdByAgentId: issueThreadInteractions.createdByAgentId, requestedResolverPolicy: issueThreadInteractions.requestedResolverPolicy, effectiveResolverPolicy: issueThreadInteractions.effectiveResolverPolicy, @@ -1207,8 +1210,9 @@ export function attentionService(db: Db, serviceOptions: AttentionServiceOptions : []; const companyAgentMap = new Map(companyAgentRows.map((agent) => [agent.id, agent])); const boardInteractionRows = interactionRows.filter((row) => - row.addresseeAgentId === null || - !evaluateAgentInvokability(companyAgentMap.get(row.addresseeAgentId), companyAgentRows).invokable + (row.addresseeAgentId === null || + !evaluateAgentInvokability(companyAgentMap.get(row.addresseeAgentId), companyAgentRows).invokable) + && (row.addresseeUserId === null || row.addresseeUserId === options.userId) ); const visibleInteractionRows = collapsePendingConfirmationsToNewest(boardInteractionRows); const [interactionIssueMap, interactionImageMap, interactionPlanDocumentMap] = await Promise.all([ diff --git a/server/src/services/issue-thread-interaction-resolution.test.ts b/server/src/services/issue-thread-interaction-resolution.test.ts index b350206f5c..b8a262acf9 100644 --- a/server/src/services/issue-thread-interaction-resolution.test.ts +++ b/server/src/services/issue-thread-interaction-resolution.test.ts @@ -12,6 +12,7 @@ function interaction(overrides: Record = {}) { createdByUserId: null, sourceRunId: "run-1", addresseeAgentId: null, + addresseeUserId: null, effectiveResolverPolicy: "anyone", ...overrides, }; @@ -50,6 +51,18 @@ describe("issue-thread interaction resolver audience", () => { expect(decision).toMatchObject({ allowed: false, code: "interaction_addressee_mismatch" }); }); + it("allows only the addressed user to resolve a user-addressed interaction", () => { + expect(evaluateIssueThreadInteractionResolverAudience({ + actor: { type: "user", userId: "alice" }, + interaction: interaction({ addresseeUserId: "alice", effectiveResolverPolicy: "human_only" }), + })).toMatchObject({ allowed: true, reason: "allow_addressee" }); + + expect(evaluateIssueThreadInteractionResolverAudience({ + actor: { type: "user", userId: "bob" }, + interaction: interaction({ addresseeUserId: "alice", effectiveResolverPolicy: "human_only" }), + })).toMatchObject({ allowed: false, code: "interaction_addressee_mismatch" }); + }); + it("requires run attribution for agents", () => { const decision = evaluateIssueThreadInteractionResolverAudience({ actor: { type: "agent", agentId: "agent-1", runId: null }, diff --git a/server/src/services/issue-thread-interaction-resolution.ts b/server/src/services/issue-thread-interaction-resolution.ts index 85e3143f1d..33031ffa64 100644 --- a/server/src/services/issue-thread-interaction-resolution.ts +++ b/server/src/services/issue-thread-interaction-resolution.ts @@ -46,6 +46,7 @@ export type IssueThreadInteractionResolverAudienceInput = { createdByUserId?: string | null; sourceRunId?: string | null; addresseeAgentId?: string | null; + addresseeUserId?: string | null; effectiveResolverPolicy: IssueThreadInteractionResolverPolicy | string; resolverPolicyProvenance?: string | null; }; @@ -180,6 +181,18 @@ export function evaluateIssueThreadInteractionResolverAudience( } if (input.actor.type === "user") { + if ( + input.interaction.addresseeUserId + && input.interaction.addresseeUserId !== input.actor.userId + ) { + return { + allowed: false, + effectiveResolverPolicy, + status: 403, + code: "interaction_addressee_mismatch", + message: "Only the addressed user may resolve this issue-thread interaction", + }; + } if ( creatorExcluded && input.interaction.createdByUserId === input.actor.userId @@ -204,7 +217,11 @@ export function evaluateIssueThreadInteractionResolverAudience( return { allowed: true, effectiveResolverPolicy, - reason: input.interaction.addresseeAgentId ? "allow_human_override" : "allow_human", + reason: input.interaction.addresseeUserId + ? "allow_addressee" + : input.interaction.addresseeAgentId + ? "allow_human_override" + : "allow_human", }; } diff --git a/server/src/services/issue-thread-interactions.ts b/server/src/services/issue-thread-interactions.ts index d279640d1b..42234de381 100644 --- a/server/src/services/issue-thread-interactions.ts +++ b/server/src/services/issue-thread-interactions.ts @@ -421,6 +421,7 @@ function isEquivalentCreateRequest( row.kind === input.kind && row.requestedResolverPolicy === input.resolverPolicy && (row.addresseeAgentId ?? null) === (input.addresseeAgentId ?? null) + && (row.addresseeUserId ?? null) === (input.addresseeUserId ?? null) && row.continuationPolicy === input.continuationPolicy && (row.idempotencyKey ?? null) === (input.idempotencyKey ?? null) && (row.sourceCommentId ?? null) === (input.sourceCommentId ?? null) @@ -478,6 +479,7 @@ function hydrateInteraction( ...row, idempotencyKey: row.idempotencyKey ?? null, addresseeAgentId: row.addresseeAgentId ?? null, + addresseeUserId: row.addresseeUserId ?? null, status: row.status as IssueThreadInteraction["status"], continuationPolicy: row.continuationPolicy as IssueThreadInteraction["continuationPolicy"], resolverPolicy: requestedResolverPolicy, @@ -2279,6 +2281,10 @@ export function issueThreadInteractionService(db: Db, opts: IssueThreadInteracti }); const normalizedData = { ...data, resolverPolicy: policy.requestedResolverPolicy }; + if (normalizedData.addresseeAgentId && normalizedData.addresseeUserId) { + throw unprocessable("An issue-thread interaction cannot address both an agent and a user"); + } + if (normalizedData.addresseeAgentId) { if (normalizedData.addresseeAgentId === actor.agentId) { throw unprocessable("Agents cannot address issue-thread interactions to themselves"); @@ -2409,6 +2415,7 @@ export function issueThreadInteractionService(db: Db, opts: IssueThreadInteracti summary: data.summary ?? null, createdByAgentId: actor.agentId ?? null, addresseeAgentId: data.addresseeAgentId ?? null, + addresseeUserId: data.addresseeUserId ?? null, createdByUserId: actor.userId ?? null, payload: data.payload, }) diff --git a/server/src/services/smoke-lab.ts b/server/src/services/smoke-lab.ts index 5c308ce261..931021f54d 100644 --- a/server/src/services/smoke-lab.ts +++ b/server/src/services/smoke-lab.ts @@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url"; import { and, desc, eq, inArray } from "drizzle-orm"; import type { Db } from "@paperclipai/db"; import { + connectionGrants, smokeRuns, smokeRunSteps, toolApplications, @@ -741,8 +742,28 @@ export function smokeLabService(db: Db, options: { lastHealthAt: now, updatedAt: now, }; + const ensureDefaultOrganizationGrant = async (connectionId: string) => { + const [existingGrant] = await db.select({ id: connectionGrants.id }).from(connectionGrants).where(and( + eq(connectionGrants.companyId, input.companyId), + eq(connectionGrants.connectionId, connectionId), + eq(connectionGrants.kind, "organization"), + eq(connectionGrants.isDefault, true), + )); + if (existingGrant) return; + await db.insert(connectionGrants).values({ + companyId: input.companyId, + connectionId, + kind: "organization", + status: "active", + isDefault: true, + credentialSecretRefs: [], + createdAt: now, + updatedAt: now, + }); + }; if (existing) { const [updated] = await db.update(toolConnections).set(values).where(eq(toolConnections.id, existing.id)).returning(); + await ensureDefaultOrganizationGrant(existing.id); return { row: updated ?? existing, created: false }; } const [created] = await db.insert(toolConnections).values({ @@ -754,6 +775,7 @@ export function smokeLabService(db: Db, options: { createdByUserId: input.actor?.actorType === "user" ? input.actor.actorId : null, createdAt: now, }).returning(); + await ensureDefaultOrganizationGrant(created.id); return { row: created, created: true }; } diff --git a/server/src/services/tool-access.ts b/server/src/services/tool-access.ts index 6614218681..7d0dceff5e 100644 --- a/server/src/services/tool-access.ts +++ b/server/src/services/tool-access.ts @@ -4,11 +4,16 @@ import { and, asc, desc, eq, gte, inArray, isNull, lt, max, ne, sql } from "driz import type { Db } from "@paperclipai/db"; import { agents, + connectionGrantMembers, + connectionGrantDelegations, connectionGrants, connectionTokenIssuances, authUsers, + companies, + companyMemberships, companySecretBindings, companySecrets, + userSecretDefinitions, heartbeatRuns, issues, issueThreadInteractions, @@ -70,7 +75,9 @@ import type { ToolConnectionHealthCheckResult, ToolConnectionHealthStatus, ToolConnectionAuthKind, + ToolConnectionCredentialPolicy, ToolConnectionTransport, + ToolCredentialSecretRef, ToolOAuthStartResult, ToolAppsAttentionResponse, ToolActionRequest, @@ -1074,6 +1081,7 @@ function toConnection(row: typeof toolConnections.$inferSelect): ToolConnection ownership: row.ownership, transport: row.transport, authKind: row.authKind, + credentialPolicy: row.credentialPolicy, status: row.status, enabled: row.enabled, config: row.config ?? {}, @@ -2247,7 +2255,8 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} projectId: runSnapshotString(snapshot, "projectId") ?? runSnapshotString(paperclipIssue, "projectId"), routineId: runSnapshotString(snapshot, "routineId"), responsibleUserId: runSnapshotString(snapshot, "responsibleUserId", "responsible_user_id") - ?? runSnapshotString(paperclipIssue, "responsibleUserId", "responsible_user_id"), + ?? runSnapshotString(paperclipIssue, "responsibleUserId", "responsible_user_id") + ?? run.responsibleUserId, }; } @@ -2324,6 +2333,72 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} }); } + async function createStandingDelegationAsk(input: { + connection: typeof toolConnections.$inferSelect; + issueId: string | null; + runId: string; + agentId: string; + ownerUserId: string; + }) { + if (!input.issueId) return; + const [company] = await db.select({ issuePrefix: companies.issuePrefix }).from(companies) + .where(eq(companies.id, input.connection.companyId)).limit(1); + const idempotencyKey = `connection-delegation:${input.connection.id}:${input.ownerUserId}:${input.agentId}`; + const payload = { + version: 1 as const, + prompt: `Allow this agent to use your ${input.connection.name} account for autonomous runs`, + acceptLabel: "Review delegation", + rejectLabel: "Not now", + detailsMarkdown: "This autonomous run is paused. Paperclip will not use your personal identity until you explicitly delegate it to this named agent.", + target: { + type: "custom" as const, + key: `connection:${input.connection.uid}:delegation:${input.ownerUserId}:${input.agentId}`, + revisionId: input.connection.updatedAt.toISOString(), + label: `Delegate ${input.connection.name}`, + href: `/${company?.issuePrefix ?? ""}/apps/${input.connection.id}/setup#personal-identity`, + }, + }; + const [existing] = await db.select({ id: issueThreadInteractions.id }).from(issueThreadInteractions).where(and( + eq(issueThreadInteractions.companyId, input.connection.companyId), + eq(issueThreadInteractions.issueId, input.issueId), + eq(issueThreadInteractions.idempotencyKey, idempotencyKey), + )).limit(1); + if (existing) { + await db.update(issueThreadInteractions).set({ + status: "pending", + continuationPolicy: "wake_assignee", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + resolverPolicyProvenance: "explicit", + effectiveResolverPolicySource: "requested", + addresseeUserId: input.ownerUserId, + payload, + result: null, + resolvedAt: null, + updatedAt: new Date(), + }).where(eq(issueThreadInteractions.id, existing.id)); + return; + } + await db.insert(issueThreadInteractions).values({ + companyId: input.connection.companyId, + issueId: input.issueId, + kind: "request_confirmation", + status: "pending", + continuationPolicy: "wake_assignee", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + resolverPolicyProvenance: "explicit", + effectiveResolverPolicySource: "requested", + idempotencyKey, + sourceRunId: input.runId, + title: `Delegate your ${input.connection.name}`, + summary: "An explicit standing delegation is required for this autonomous run.", + createdByAgentId: input.agentId, + addresseeUserId: input.ownerUserId, + payload, + }); + } + async function enforceDefaultConnectionTokenRateLimit(input: { connection: typeof toolConnections.$inferSelect; agentId: string; @@ -3037,10 +3112,21 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} .where(and(eq(toolProfiles.companyId, connection.companyId), eq(toolProfiles.profileKey, profileKey))) .limit(1); if (!profile) { + const [sameName] = await dbClient + .select({ id: toolProfiles.id }) + .from(toolProfiles) + .where(and( + eq(toolProfiles.companyId, connection.companyId), + eq(toolProfiles.name, connection.name), + )) + .limit(1); + const profileName = sameName + ? `${connection.name} (${connection.id.replace(/-/g, "").slice(0, 8)})` + : connection.name; [profile] = await dbClient.insert(toolProfiles).values({ companyId: connection.companyId, profileKey, - name: connection.name, + name: profileName, description: `Access profile for ${connection.name}.`, status: "active", defaultAction: "deny", @@ -3067,15 +3153,17 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} connection: typeof toolConnections.$inferSelect; newCatalogEntryIds: string[]; activeCatalogEntryIds: string[]; + restoreDraftDefaults?: boolean; actor?: ActorInfo; }) { // Catalog discovery also runs while the setup wizard is still a draft. // Access is not granted until the operator finishes that wizard, so a // draft refresh must never manufacture a profile or company-wide binding. // Active legacy connections may still need the managed profile created on - // their first refresh, which is why the guard is on lifecycle state rather - // than profile existence. - if (input.connection.status !== "active") return; + // their first refresh. A reconnect is the one draft exception: removal + // deliberately clears the old selections, so reconnecting restores the + // documented defaults without activating the connection itself. + if (input.connection.status !== "active" && !input.restoreDraftDefaults) return; const profileKey = `app:${input.connection.id}`; let [profile] = await db .select() @@ -3087,10 +3175,21 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} .limit(1); const createdProfile = !profile; if (!profile) { + const [sameName] = await db + .select({ id: toolProfiles.id }) + .from(toolProfiles) + .where(and( + eq(toolProfiles.companyId, input.connection.companyId), + eq(toolProfiles.name, input.connection.name), + )) + .limit(1); + const profileName = sameName + ? `${input.connection.name} (${input.connection.id.replace(/-/g, "").slice(0, 8)})` + : input.connection.name; [profile] = await db.insert(toolProfiles).values({ companyId: input.connection.companyId, profileKey, - name: input.connection.name, + name: profileName, description: `Access profile for ${input.connection.name}.`, status: "active", defaultAction: "deny", @@ -3207,14 +3306,14 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} return row; } - async function ensureDefaultWorkspaceGrant(connection: typeof toolConnections.$inferSelect) { + async function ensureDefaultOrganizationGrant(connection: typeof toolConnections.$inferSelect) { const [existing] = await db .select() .from(connectionGrants) .where(and( eq(connectionGrants.companyId, connection.companyId), eq(connectionGrants.connectionId, connection.id), - eq(connectionGrants.kind, "workspace"), + eq(connectionGrants.kind, "organization"), eq(connectionGrants.isDefault, true), )) .limit(1); @@ -3224,7 +3323,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} .values({ companyId: connection.companyId, connectionId: connection.id, - kind: "workspace", + kind: "organization", credentialSecretRefs: connection.credentialSecretRefs, status: "active", isDefault: true, @@ -3412,7 +3511,16 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} await createProfileEntries(companyId, profileId, entries); } - async function syncCredentialBindings(connection: typeof toolConnections.$inferSelect) { + /** + * @param grantSecretRefs Secret refs held by a grant rather than the connection + * row. A personal credential lives only on its user grant (PAP-17835), so it + * would otherwise have no `company_secret_bindings` row and drop out of + * secret projection and removal teardown. + */ + async function syncCredentialBindings( + connection: typeof toolConnections.$inferSelect, + grantSecretRefs: ToolCredentialSecretRef[] = [], + ) { await db .delete(companySecretBindings) .where( @@ -3429,7 +3537,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} projectionClass: "unclassified", projectionAllowlistKey: null, })), - ...connection.credentialSecretRefs.map((ref) => ({ + ...[...connection.credentialSecretRefs, ...grantSecretRefs].map((ref) => ({ secretId: ref.secretId, configPath: ref.configPath, projectionClass: ref.projectionClass ?? "unclassified", @@ -4114,7 +4222,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} async function refreshCatalog( connectionId: string, actor?: ActorInfo, - refreshOptions: { enableAllByDefault?: boolean } = {}, + refreshOptions: { enableAllByDefault?: boolean; restoreDraftDefaults?: boolean } = {}, ): Promise { const connection = await getConnectionRow(connectionId); const refreshedAt = now(); @@ -4273,17 +4381,9 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} }) .map((entry) => entry.id), activeCatalogEntryIds: activeEntries.map((entry) => entry.id), + restoreDraftDefaults: refreshOptions.restoreDraftDefaults, actor, }); - if (refreshOptions.enableAllByDefault) { - await upsertAskFirstPolicies({ - companyId: updatedConnection.companyId, - connection: updatedConnection, - askFirstEntries: [], - actor, - }); - } - await audit({ companyId: connection.companyId, connectionId: connection.id, @@ -4604,6 +4704,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} }) .where(eq(toolConnections.id, existing.id)) .returning(); + await ensureDefaultOrganizationGrant(updated); await syncCredentialBindings(updated); await ensureRuntimeSlot(updated); return { row: updated, created: false }; @@ -4624,6 +4725,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} credentialRefs: [], credentialSecretRefs: [], }).returning(); + await ensureDefaultOrganizationGrant(created); await syncCredentialBindings(created); await ensureRuntimeSlot(created); return { row: created, created: true }; @@ -5364,6 +5466,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} value: string; actor?: ActorInfo; existingRefs?: typeof connectionGrants.$inferSelect.credentialSecretRefs; + ownerUserId?: string; }) { const existing = input.existingRefs === undefined ? oauthSecretRef(input.connection, input.configPath) @@ -5372,6 +5475,45 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} await secrets.rotate(existing.secretId, { value: input.value }, actorForSecret(input.actor)); return existing; } + if (input.ownerUserId) { + const definitionKey = `tool_oauth.${input.connection.id}.${input.configPath.replace(/[^a-z0-9_:-]+/gi, "_")}`; + let [definition] = await db.select().from(userSecretDefinitions).where(and( + eq(userSecretDefinitions.companyId, input.companyId), + eq(userSecretDefinitions.key, definitionKey), + isNull(userSecretDefinitions.deletedAt), + )).limit(1); + if (!definition) { + [definition] = await db.insert(userSecretDefinitions).values({ + companyId: input.companyId, + key: definitionKey, + name: `${input.connection.name} ${input.label}`, + description: `Personal OAuth ${input.label.toLowerCase()} for ${input.connection.name}.`, + provider: "local_encrypted", + managedMode: "paperclip_managed", + createdByAgentId: input.actor?.actorType === "agent" ? input.actor.actorId : null, + createdByUserId: input.actor?.actorType === "user" ? input.actor.actorId : null, + }).onConflictDoNothing().returning(); + if (!definition) { + [definition] = await db.select().from(userSecretDefinitions).where(and( + eq(userSecretDefinitions.companyId, input.companyId), + eq(userSecretDefinitions.key, definitionKey), + isNull(userSecretDefinitions.deletedAt), + )).limit(1); + } + } + if (!definition) throw new Error("Failed to create personal OAuth secret definition"); + const secret = await secrets.createCurrentUserSecretValue(input.companyId, input.ownerUserId, { + definitionId: definition.id, + value: input.value, + }, actorForSecret(input.actor)); + return { + secretId: secret.id, + versionSelector: "latest" as const, + configPath: input.configPath, + required: input.configPath === "oauth.access_token", + label: input.label, + }; + } const secret = await secrets.create(input.companyId, { name: `${input.connection.name} ${input.label} ${randomUUID().slice(0, 8)}`, key: `tool_app.${randomUUID()}.${input.configPath.replace(/[^a-z0-9_:-]+/gi, "_")}`, @@ -6401,10 +6543,10 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} sourceTemplateKey: galleryEntry.slug, connectionMethodKey: method?.key, methodConfig: normalizedMethodConfig?.values ?? {}, - // PostHog's documented safe default keeps reads available while new - // write/destructive actions wait for review. Other curated apps keep - // the all-tools default selected by the setup wizard. - quarantineNewEntries: galleryEntry.slug === "posthog", + // Grant-backed setup keeps the full discovered catalog selectable; + // the wizard projects the app's ask-first defaults into policies at + // finish time instead of using catalog quarantine as access state. + quarantineNewEntries: false, ...(galleryEntry.slug === "posthog" ? { safeDefault: true } : {}), } : { ...baseConfig, quarantineNewEntries: false, unverifiedServer: true }; @@ -6458,6 +6600,20 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} const credentialSecretRefs: CreateToolConnection["credentialSecretRefs"] = []; const credentialRefs: McpConnectionCredentialRef[] = []; const createdSecretIds: string[] = []; + // "Just me" needs a named board user to own the consent. An agent actor + // cannot hold a personal identity, and silently falling back to a shared + // credential is exactly the mis-scoping the design forbids, so refuse. + const personalIdentityUserId = input.grantKind === "user" + ? (actor?.actorType === "user" && actor.actorId ? actor.actorId : null) + : null; + if (input.grantKind === "user" && !personalIdentityUserId) { + throw badRequest("Connecting an app as yourself requires a signed-in user"); + } + // Only the personal path changes the policy; every existing gallery app keeps + // the shared default it has today. + const credentialPolicy: ToolConnectionCredentialPolicy | undefined = personalIdentityUserId + ? "per_user" + : undefined; let applicationRow: typeof toolApplications.$inferSelect | null = null; let connectionRow: typeof toolConnections.$inferSelect | null = null; let revivedConnectionPrevious: typeof toolConnections.$inferSelect | null = null; @@ -6556,6 +6712,11 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} .limit(1); revivedConnectionPrevious = archived ?? null; } + // A personal credential never becomes the connection's shared secret: the + // row carries the header shape only, and the secret refs go to the user + // grant below. `ensureDefaultOrganizationGrant` copies this list, so + // leaving it empty is what keeps the secret off an organization grant. + const connectionCredentialSecretRefs = personalIdentityUserId ? [] : credentialSecretRefs; if (revivedConnectionPrevious) { [connectionRow] = await db.update(toolConnections).set({ name, @@ -6566,7 +6727,8 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} config, transportConfig: config, credentialRefs, - credentialSecretRefs, + credentialSecretRefs: connectionCredentialSecretRefs, + ...(credentialPolicy ? { credentialPolicy } : {}), updatedAt: new Date(), }).where(eq(toolConnections.id, revivedConnectionPrevious.id)).returning(); } else { @@ -6585,12 +6747,57 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} config, transportConfig: config, credentialRefs, - credentialSecretRefs, + credentialSecretRefs: connectionCredentialSecretRefs, + ...(credentialPolicy ? { credentialPolicy } : {}), createdByAgentId: actor?.actorType === "agent" ? actor.actorId ?? null : null, createdByUserId: actor?.actorType === "user" ? actor.actorId ?? null : null, }).returning(); } - await syncCredentialBindings(connectionRow); + if (personalIdentityUserId) { + // "Just me" (PAP-17835 seam #4). The credential is committed straight to + // the caller's own grant; the connection row keeps only the header + // *shape* in `credentialRefs` (which the gateway reads for placement and + // then resolves against the acting user's grant) and no shared secret + // refs at all. Two consequences the design requires: + // - `ensureDefaultOrganizationGrant` is skipped, so no organization + // grant is created first and later "moved" to a user grant. It stays + // absent until someone explicitly connects an organization identity, + // which is what makes "Organization identity · Not connected" + // truthful rather than a silent fallback. + // - the credential never lands in `connection.credentialSecretRefs`, + // which is what an organization grant would have copied. + // + // OAuth is the exception: no credential exists yet at connect time, and + // the callback upserts this same (connection, user, subject) grant with + // the tokens. Pre-creating an empty `active` grant there would render as + // "Connected" with nothing behind it, so the grant is left to the + // callback and only the organization grant is suppressed. + if (credentialSecretRefs.length > 0) { + await db.insert(connectionGrants).values({ + companyId, + connectionId: connectionRow.id, + kind: "user", + subjectUserId: personalIdentityUserId, + credentialSecretRefs, + status: "active", + isDefault: false, + createdByUserId: personalIdentityUserId, + }); + await db.insert(toolAccessAuditEvents).values({ + companyId, + connectionId: connectionRow.id, + actorType: "user", + actorId: personalIdentityUserId, + action: "connection_grant.created", + outcome: "success", + reasonCode: "personal_identity_created", + details: { kind: "user", credentialSecretRefCount: credentialSecretRefs.length }, + }); + } + } else { + await ensureDefaultOrganizationGrant(connectionRow); + } + await syncCredentialBindings(connectionRow, personalIdentityUserId ? credentialSecretRefs : []); await ensureRuntimeSlot(connectionRow); if (galleryEntry && method?.auth === "oauth") { @@ -6644,7 +6851,11 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} } throw error; } - const refresh = await refreshCatalog(connectionRow.id, actor, { enableAllByDefault: true }); + const restoreDraftDefaults = Boolean(revivedConnectionPrevious); + const refresh = await refreshCatalog(connectionRow.id, actor, { + enableAllByDefault: restoreDraftDefaults, + restoreDraftDefaults, + }); const [application] = await db.select().from(toolApplications).where(eq(toolApplications.id, applicationRow.id)); return { connectionId: refresh.connection.id, @@ -6725,6 +6936,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} connection: typeof toolConnections.$inferSelect; askFirstEntries: Array; actor?: ActorInfo; + disableStale?: boolean; }, dbClient: ToolAccessMutationDb = db): Promise { const existingPolicies = await dbClient .select() @@ -6780,15 +6992,17 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} results.push(toPolicy(created)); } } - const stalePolicies = managedPolicies.filter((policy) => { - const config = asRecord(policy.config); - return typeof config.catalogEntryId === "string" && !askFirstIds.has(config.catalogEntryId); - }); - for (const policy of stalePolicies) { - await dbClient - .update(toolPolicies) - .set({ enabled: false, updatedAt: new Date() }) - .where(eq(toolPolicies.id, policy.id)); + if (input.disableStale !== false) { + const stalePolicies = managedPolicies.filter((policy) => { + const config = asRecord(policy.config); + return typeof config.catalogEntryId === "string" && !askFirstIds.has(config.catalogEntryId); + }); + for (const policy of stalePolicies) { + await dbClient + .update(toolPolicies) + .set({ enabled: false, updatedAt: new Date() }) + .where(eq(toolPolicies.id, policy.id)); + } } return results; } @@ -7086,7 +7300,28 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} .returning(); await syncCredentialBindings(updated); const health = await checkConnectionHealth(updated.id, actor); + const catalogBefore = await db + .select({ id: toolCatalogEntries.id, riskLevel: toolCatalogEntries.riskLevel }) + .from(toolCatalogEntries) + .where(eq(toolCatalogEntries.connectionId, updated.id)); const refresh = await refreshCatalog(updated.id, actor, { enableAllByDefault: true }); + const previousRiskByCatalogId = new Map(catalogBefore.map((entry) => [entry.id, entry.riskLevel])); + const newAskFirstIds = refresh.catalog + .filter((entry) => { + if (entry.riskLevel !== "write" && entry.riskLevel !== "destructive") return false; + const previousRisk = previousRiskByCatalogId.get(entry.id); + return previousRisk === undefined || previousRisk === "read"; + }) + .map((entry) => entry.id); + if (newAskFirstIds.length > 0) { + await upsertAskFirstPolicies({ + companyId, + connection: updated, + askFirstEntries: await assertCatalogEntriesForConnection(companyId, updated.id, newAskFirstIds), + actor, + disableStale: false, + }); + } return { ...health, connection: refresh.connection }; } @@ -7162,17 +7397,39 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} if (input.subjectUserId && input.issueId && binding.actorType === "agent") { const idempotencyKey = `connection-authorization:${connection.id}:${input.subjectUserId}`; + // Provider label for the card's copy. The gallery definition's name when we + // have one, else the connection's own name — never a secret name or ref. + const sourceTemplateKey = typeof connection.config.sourceTemplateKey === "string" + ? connection.config.sourceTemplateKey + : null; + const providerName = (sourceTemplateKey ? getConnectableAppDefinition(sourceTemplateKey)?.name : null) + ?? connection.name; + const [requestingAgent] = binding.actorId + ? await db.select({ name: agents.name }).from(agents).where(and( + eq(agents.id, binding.actorId), + eq(agents.companyId, companyId), + )).limit(1) + : [undefined]; const payload = { version: 1 as const, - prompt: `Connect your account to ${connection.name}`, - acceptLabel: "Open authorization", + prompt: `Connect your ${providerName} to continue`, + acceptLabel: `Connect ${providerName}`, rejectLabel: "Not now", detailsMarkdown: "Authorization is required before this agent can act on your behalf.", + // Presentation metadata so the card can compose its own copy instead of + // parsing the title string (PAP-17835 seam #6). The interaction kind and + // the server-addressed audience are unchanged. + connectionAuthorization: { + version: 1 as const, + providerName, + connectionName: connection.name === providerName ? null : connection.name, + requestingAgentName: requestingAgent?.name ?? null, + }, target: { type: "custom" as const, key: `connection:${connection.uid}:user:${input.subjectUserId}`, revisionId: state, - label: `Connect ${connection.name}`, + label: `Connect ${providerName}`, href: authorizationUrl.toString(), }, }; @@ -7184,6 +7441,11 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} const [interaction] = existingInteraction ? await db.update(issueThreadInteractions).set({ status: "pending", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + resolverPolicyProvenance: "explicit", + effectiveResolverPolicySource: "requested", + addresseeUserId: input.subjectUserId, payload, result: null, resolvedAt: null, @@ -7195,10 +7457,15 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} kind: "request_confirmation", status: "pending", continuationPolicy: "none", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + resolverPolicyProvenance: "explicit", + effectiveResolverPolicySource: "requested", + addresseeUserId: input.subjectUserId, idempotencyKey, sourceRunId: binding.actorType === "agent" ? input.actor.sessionId ?? null : null, - title: "Connect your account", - summary: `Connect ${connection.name} to continue`, + title: `Connect your ${providerName} to continue`, + summary: `${requestingAgent?.name ?? "An agent"} needs your ${providerName} identity for work running as you.`, createdByAgentId: binding.actorType === "agent" ? binding.actorId : null, payload, }).returning(); @@ -7398,17 +7665,111 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} code: input.code, resource: endpoints.resource, }); - const [existingUserGrant] = stateRow.subjectUserId - ? await db.select().from(connectionGrants).where(and( + if (stateRow.subjectUserId) { + return db.transaction(async (tx) => { + // Serialize callback persistence with suspension/removal. Those paths + // lock this same membership row before sweeping personal credentials. + const [membership] = await tx.select({ id: companyMemberships.id }).from(companyMemberships).where(and( + eq(companyMemberships.companyId, connection.companyId), + eq(companyMemberships.principalType, "user"), + eq(companyMemberships.principalId, stateRow.subjectUserId!), + eq(companyMemberships.status, "active"), + )).limit(1).for("update"); + if (!membership) { + throw forbidden("Your company membership is no longer active. Ask a company owner to restore access before you authorize this connection again."); + } + + const [existingUserGrant] = await tx.select().from(connectionGrants).where(and( eq(connectionGrants.companyId, connection.companyId), eq(connectionGrants.connectionId, connection.id), eq(connectionGrants.kind, "user"), - eq(connectionGrants.subjectUserId, stateRow.subjectUserId), - )).limit(1) - : [undefined]; - const subjectCredentialSecretRefs = stateRow.subjectUserId - ? existingUserGrant?.credentialSecretRefs ?? [] - : connection.credentialSecretRefs; + eq(connectionGrants.subjectUserId, stateRow.subjectUserId!), + )).limit(1); + const subjectCredentialSecretRefs = existingUserGrant?.credentialSecretRefs ?? []; + const accessRef = await createOrRotateOAuthSecret({ + companyId: connection.companyId, + connection, + configPath: "oauth.access_token", + label: "OAuth access token", + value: token.accessToken, + actor: input.actor, + existingRefs: subjectCredentialSecretRefs, + ownerUserId: stateRow.subjectUserId!, + }); + const nextCredentialSecretRefs = [ + ...subjectCredentialSecretRefs.filter((ref) => ref.configPath !== "oauth.access_token" && ref.configPath !== "oauth.refresh_token"), + accessRef, + ]; + if (token.refreshToken) { + nextCredentialSecretRefs.push(await createOrRotateOAuthSecret({ + companyId: connection.companyId, + connection, + configPath: "oauth.refresh_token", + label: "OAuth refresh token", + value: token.refreshToken, + actor: input.actor, + existingRefs: subjectCredentialSecretRefs, + ownerUserId: stateRow.subjectUserId!, + })); + } else { + const existingRefreshRef = subjectCredentialSecretRefs.find((ref) => ref.configPath === "oauth.refresh_token"); + if (existingRefreshRef) nextCredentialSecretRefs.push(existingRefreshRef); + } + + const grantValues = { + credentialSecretRefs: nextCredentialSecretRefs, + status: "active" as const, + revokedAt: null, + revokedByAgentId: null, + revokedByUserId: null, + updatedAt: new Date(), + }; + if (existingUserGrant) { + await tx.update(connectionGrants).set(grantValues).where(eq(connectionGrants.id, existingUserGrant.id)); + } else { + await tx.insert(connectionGrants).values({ + companyId: connection.companyId, + connectionId: connection.id, + kind: "user", + subjectUserId: stateRow.subjectUserId!, + ...grantValues, + isDefault: false, + createdByUserId: stateRow.subjectUserId!, + }); + } + if (stateRow.interactionId) { + await tx.update(issueThreadInteractions).set({ + status: "accepted", + result: { version: 1, outcome: "accepted" }, + resolvedByUserId: stateRow.subjectUserId!, + resolvedAt: new Date(), + updatedAt: new Date(), + }).where(and( + eq(issueThreadInteractions.id, stateRow.interactionId), + eq(issueThreadInteractions.companyId, connection.companyId), + )); + } + const [application] = await tx.select().from(toolApplications).where(eq(toolApplications.id, connection.applicationId)); + if (!application) throw new Error("OAuth connection application was not found"); + const catalog = (await tx.select().from(toolCatalogEntries).where(and( + eq(toolCatalogEntries.companyId, connection.companyId), + eq(toolCatalogEntries.connectionId, connection.id), + ))).map(toCatalogEntry); + return { + connectionId: connection.id, + application: toApplication(application), + connection: toConnection(connection), + catalog, + actions: groupedActions(catalog), + suggestedDefaults: galleryEntry + ? recommendedDefaultsForApp(galleryEntry, connectionMethodForConnection(galleryEntry, connection).key) + : { access: "all_agents", askFirstRiskLevels: ["write", "destructive"] }, + auth: null, + }; + }); + } + + const subjectCredentialSecretRefs = connection.credentialSecretRefs; const accessRef = await createOrRotateOAuthSecret({ companyId: connection.companyId, connection, @@ -7416,7 +7777,6 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} label: "OAuth access token", value: token.accessToken, actor: input.actor, - existingRefs: stateRow.subjectUserId ? subjectCredentialSecretRefs : undefined, }); const nextCredentialSecretRefs = [ ...subjectCredentialSecretRefs.filter((ref) => ref.configPath !== "oauth.access_token" && ref.configPath !== "oauth.refresh_token"), @@ -7430,65 +7790,12 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} label: "OAuth refresh token", value: token.refreshToken, actor: input.actor, - existingRefs: stateRow.subjectUserId ? subjectCredentialSecretRefs : undefined, })); } else { const existingRefreshRef = subjectCredentialSecretRefs.find((ref) => ref.configPath === "oauth.refresh_token"); if (existingRefreshRef) nextCredentialSecretRefs.push(existingRefreshRef); } const expiresAt = token.expiresIn ? new Date(Date.now() + token.expiresIn * 1000).toISOString() : null; - if (stateRow.subjectUserId) { - const grantValues = { - credentialSecretRefs: nextCredentialSecretRefs, - status: "active" as const, - revokedAt: null, - revokedByAgentId: null, - revokedByUserId: null, - updatedAt: new Date(), - }; - if (existingUserGrant) { - await db.update(connectionGrants).set(grantValues).where(eq(connectionGrants.id, existingUserGrant.id)); - } else { - await db.insert(connectionGrants).values({ - companyId: connection.companyId, - connectionId: connection.id, - kind: "user", - subjectUserId: stateRow.subjectUserId, - ...grantValues, - isDefault: false, - createdByUserId: stateRow.subjectUserId, - }); - } - if (stateRow.interactionId) { - await db.update(issueThreadInteractions).set({ - status: "accepted", - result: { version: 1, outcome: "accepted" }, - resolvedByUserId: stateRow.subjectUserId, - resolvedAt: new Date(), - updatedAt: new Date(), - }).where(and( - eq(issueThreadInteractions.id, stateRow.interactionId), - eq(issueThreadInteractions.companyId, connection.companyId), - )); - } - const [application] = await db.select().from(toolApplications).where(eq(toolApplications.id, connection.applicationId)); - if (!application) throw new Error("OAuth connection application was not found"); - const catalog = (await db.select().from(toolCatalogEntries).where(and( - eq(toolCatalogEntries.companyId, connection.companyId), - eq(toolCatalogEntries.connectionId, connection.id), - ))).map(toCatalogEntry); - return { - connectionId: connection.id, - application: toApplication(application), - connection: toConnection(connection), - catalog, - actions: groupedActions(catalog), - suggestedDefaults: galleryEntry - ? recommendedDefaultsForApp(galleryEntry, connectionMethodForConnection(galleryEntry, connection).key) - : { access: "all_agents", askFirstRiskLevels: ["write", "destructive"] }, - auth: null, - }; - } const nextConfig = { ...connection.config, oauth: { @@ -7980,6 +8287,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} ownership: input.ownership ?? "customer", transport, authKind: input.authKind ?? "none", + credentialPolicy: input.credentialPolicy ?? (input.authKind === "oauth" ? "per_user" : "shared"), status: input.status ?? "draft", enabled: input.enabled ?? false, config, @@ -7989,7 +8297,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} createdByAgentId: binding.actorType === "agent" ? binding.actorId : null, createdByUserId: binding.actorType === "user" ? binding.actorId : null, }).returning(); - await ensureDefaultWorkspaceGrant(row); + await ensureDefaultOrganizationGrant(row); await syncCredentialBindings(row); await ensureRuntimeSlot(row); return toConnection(row); @@ -8007,7 +8315,257 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} eq(connectionGrants.companyId, connection.companyId), eq(connectionGrants.connectionId, connection.id), )).orderBy(desc(connectionGrants.isDefault), desc(connectionGrants.updatedAt)); - return { connection: { id: connection.id, uid: connection.uid }, grants }; + const grantIds = grants.map((grant) => grant.id); + const [members, delegations] = grantIds.length === 0 ? [[], []] : await Promise.all([ + db.select().from(connectionGrantMembers).where(and( + eq(connectionGrantMembers.companyId, connection.companyId), + inArray(connectionGrantMembers.grantId, grantIds), + )), + db.select().from(connectionGrantDelegations).where(and( + eq(connectionGrantDelegations.companyId, connection.companyId), + inArray(connectionGrantDelegations.grantId, grantIds), + )), + ]); + return { + connection: { id: connection.id, uid: connection.uid }, + grants: grants.map((grant) => ({ + ...grant, + members: members.filter((member) => member.grantId === grant.id), + delegations: delegations.filter((delegation) => delegation.grantId === grant.id), + })), + }; + }, + + /** + * Company members eligible to appear in an organization grant's audience. + * The audience editor needs display names, and the client must not have to + * cross-reference a second endpoint to render "12 selected members". + */ + listConnectionAudienceMembers: async (companyId: string) => { + const rows = await db + .select({ + userId: companyMemberships.principalId, + name: authUsers.name, + email: authUsers.email, + }) + .from(companyMemberships) + .leftJoin(authUsers, eq(authUsers.id, companyMemberships.principalId)) + .where(and( + eq(companyMemberships.companyId, companyId), + eq(companyMemberships.principalType, "user"), + eq(companyMemberships.status, "active"), + )); + return rows + .map((row) => ({ userId: row.userId, name: row.name ?? null, email: row.email ?? null })) + .sort((a, b) => (a.name ?? a.email ?? a.userId).localeCompare(b.name ?? b.email ?? b.userId)); + }, + + /** + * Replace an organization grant's audience atomically (PAP-17835). + * + * An empty `memberUserIds` persists as zero rows, which the resolver already + * treats as "every organization member". Replacement is delete-then-insert + * inside one transaction so a partially-applied audience can never widen or + * narrow access, and every member id is checked against active company + * membership first so an audience cannot name an outsider. Existing audience + * members are locked too: membership cleanup deliberately retains a sole + * inactive audience row, and a concurrent empty replacement must not erase + * that fail-closed marker after cleanup wins. Once cleanup has retained that + * marker, callers must restore the member or replace it with an active named + * audience before widening access to the whole company. + */ + replaceConnectionGrantMembers: async ( + idOrUid: string, + grantId: string, + memberUserIds: string[], + actor?: ActorInfo, + ) => { + const connection = await getConnectionRow(idOrUid); + const [grant] = await db.select().from(connectionGrants).where(and( + eq(connectionGrants.id, grantId), + eq(connectionGrants.companyId, connection.companyId), + eq(connectionGrants.connectionId, connection.id), + )).limit(1); + if (!grant) throw notFound("Connection grant not found"); + if (grant.kind !== "organization") { + throw badRequest("Only an organization identity has an audience; a personal identity belongs to its owner"); + } + const requested = [...new Set(memberUserIds.map((id) => id.trim()).filter(Boolean))]; + const binding = actorBinding(actor); + const members = await db.transaction(async (tx) => { + // Serialize replacements before taking the current audience snapshot. + // Otherwise a second replacement could read the old rows, wait behind + // an in-flight replacement, and then miss locking a newly-added member. + await tx + .select({ id: connectionGrants.id }) + .from(connectionGrants) + .where(and( + eq(connectionGrants.id, grant.id), + eq(connectionGrants.companyId, connection.companyId), + eq(connectionGrants.connectionId, connection.id), + )) + .for("update"); + const existingAudience = await tx + .select({ subjectId: connectionGrantMembers.subjectId }) + .from(connectionGrantMembers) + .where(and( + eq(connectionGrantMembers.companyId, connection.companyId), + eq(connectionGrantMembers.grantId, grant.id), + eq(connectionGrantMembers.subjectType, "user"), + )); + const existingUserIds = [...new Set(existingAudience.map((row) => row.subjectId))]; + const membershipUserIds = [...new Set([...existingUserIds, ...requested])]; + if (membershipUserIds.length > 0) { + // Membership suspension/archive/removal takes the same row lock before + // sweeping grant audiences. Lock both the old and new audience so an + // empty replacement also serializes with cleanup of its existing sole + // member. Whichever transaction wins is then authoritative. + const memberships = await tx + .select({ + principalId: companyMemberships.principalId, + status: companyMemberships.status, + }) + .from(companyMemberships) + .where(and( + eq(companyMemberships.companyId, connection.companyId), + eq(companyMemberships.principalType, "user"), + inArray(companyMemberships.principalId, membershipUserIds), + )) + .orderBy(asc(companyMemberships.id)) + .for("update"); + const active = new Set( + memberships.filter((row) => row.status === "active").map((row) => row.principalId), + ); + const unknown = requested.filter((id) => !active.has(id)); + if (unknown.length > 0) { + throw unprocessable("Every audience member must be an active member of this company", { + code: "audience_member_not_in_company", + unknownUserIds: unknown, + }); + } + const inactiveExisting = existingUserIds.filter((id) => !active.has(id)); + if (requested.length === 0 && inactiveExisting.length > 0) { + throw conflict("Replace inactive audience members before widening access to the whole company", { + code: "audience_widening_blocked", + inactiveUserIds: inactiveExisting, + }); + } + } + await tx.delete(connectionGrantMembers).where(and( + eq(connectionGrantMembers.companyId, connection.companyId), + eq(connectionGrantMembers.grantId, grant.id), + )); + const inserted = requested.length === 0 + ? [] + : await tx.insert(connectionGrantMembers).values(requested.map((subjectId) => ({ + companyId: connection.companyId, + grantId: grant.id, + subjectType: "user" as const, + subjectId, + }))).returning(); + await tx.update(connectionGrants) + .set({ updatedAt: new Date() }) + .where(eq(connectionGrants.id, grant.id)); + return inserted; + }); + await db.insert(toolAccessAuditEvents).values({ + companyId: connection.companyId, + connectionId: connection.id, + actorType: binding.actorType ?? "system", + actorId: binding.actorId, + action: "connection_grant.audience_replaced", + outcome: "success", + reasonCode: "audience_replaced", + details: { grantId: grant.id, memberCount: members.length, memberUserIds: requested }, + }); + return { ...grant, members }; + }, + + createConnectionGrantDelegation: async ( + idOrUid: string, + grantId: string, + agentId: string, + ownerUserId: string, + ) => { + const connection = await getConnectionRow(idOrUid); + return db.transaction(async (tx) => { + // Membership removal/suspension takes this same row lock before sweeping + // personal grants. Whichever operation wins is therefore authoritative: + // removal deletes a delegation committed first, while creation that runs + // second observes the inactive membership and fails closed. + const [membership] = await tx.select({ id: companyMemberships.id }) + .from(companyMemberships) + .where(and( + eq(companyMemberships.companyId, connection.companyId), + eq(companyMemberships.principalType, "user"), + eq(companyMemberships.principalId, ownerUserId), + eq(companyMemberships.status, "active"), + )) + .for("update") + .limit(1); + if (!membership) { + throw forbidden("Only an active company member can delegate their personal grant"); + } + + const [grant] = await tx.select().from(connectionGrants).where(and( + eq(connectionGrants.id, grantId), + eq(connectionGrants.companyId, connection.companyId), + eq(connectionGrants.connectionId, connection.id), + eq(connectionGrants.kind, "user"), + eq(connectionGrants.subjectUserId, ownerUserId), + eq(connectionGrants.status, "active"), + )).limit(1); + if (!grant) throw forbidden("Only the active personal grant owner can create a delegation"); + const [targetAgent] = await tx.select({ id: agents.id }).from(agents).where(and( + eq(agents.id, agentId), + eq(agents.companyId, connection.companyId), + )).limit(1); + if (!targetAgent) throw notFound("Agent not found"); + const [existing] = await tx.select().from(connectionGrantDelegations).where(and( + eq(connectionGrantDelegations.grantId, grant.id), + eq(connectionGrantDelegations.agentId, agentId), + )).limit(1); + if (existing) return existing; + const [delegation] = await tx.insert(connectionGrantDelegations).values({ + companyId: connection.companyId, + grantId: grant.id, + agentId, + createdByUserId: ownerUserId, + }).returning(); + await tx.insert(toolAccessAuditEvents).values({ + companyId: connection.companyId, + connectionId: connection.id, + actorType: "user", + actorId: ownerUserId, + action: "connection_grant.delegated", + outcome: "success", + reasonCode: "delegation_created", + details: { grantId: grant.id, delegationId: delegation!.id, agentId }, + }); + return delegation!; + }); + }, + + revokeConnectionGrantDelegation: async (idOrUid: string, grantId: string, delegationId: string, actor?: ActorInfo) => { + const connection = await getConnectionRow(idOrUid); + const [delegation] = await db.delete(connectionGrantDelegations).where(and( + eq(connectionGrantDelegations.id, delegationId), + eq(connectionGrantDelegations.companyId, connection.companyId), + eq(connectionGrantDelegations.grantId, grantId), + )).returning(); + if (!delegation) throw notFound("Connection grant delegation not found"); + const binding = actorBinding(actor); + await db.insert(toolAccessAuditEvents).values({ + companyId: connection.companyId, + connectionId: connection.id, + actorType: binding.actorType ?? "system", + actorId: binding.actorId, + action: "connection_grant.delegation_revoked", + outcome: "success", + reasonCode: "delegation_revoked", + details: { grantId, delegationId, agentId: delegation.agentId }, + }); + return delegation; }, addConnectionInstallation: async (idOrUid: string, input: { @@ -8020,14 +8578,14 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} if (input.isDefault) { await db.update(connectionGrants).set({ isDefault: false, updatedAt: new Date() }).where(and( eq(connectionGrants.connectionId, connection.id), - eq(connectionGrants.kind, "workspace"), + eq(connectionGrants.kind, "organization"), )); } const binding = actorBinding(actor); const [grant] = await db.insert(connectionGrants).values({ companyId: connection.companyId, connectionId: connection.id, - kind: "workspace", + kind: "organization", providerTenant: input.providerTenant, credentialSecretRefs: input.credentialSecretRefs ?? [], status: "active", @@ -8052,18 +8610,38 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} revokeConnectionGrant: async (idOrUid: string, grantId: string, actor?: ActorInfo) => { const connection = await getConnectionRow(idOrUid); const binding = actorBinding(actor); - const [grant] = await db.update(connectionGrants).set({ - status: "revoked", - isDefault: false, - revokedAt: new Date(), - revokedByAgentId: binding.actorType === "agent" ? binding.actorId : null, - revokedByUserId: binding.actorType === "user" ? binding.actorId : null, - updatedAt: new Date(), - }).where(and( - eq(connectionGrants.id, grantId), - eq(connectionGrants.companyId, connection.companyId), - eq(connectionGrants.connectionId, connection.id), - )).returning(); + const grant = await db.transaction(async (tx) => { + const removedDelegations = await tx.delete(connectionGrantDelegations).where(and( + eq(connectionGrantDelegations.companyId, connection.companyId), + eq(connectionGrantDelegations.grantId, grantId), + )).returning(); + const [updated] = await tx.update(connectionGrants).set({ + status: "revoked", + isDefault: false, + revokedAt: new Date(), + revokedByAgentId: binding.actorType === "agent" ? binding.actorId : null, + revokedByUserId: binding.actorType === "user" ? binding.actorId : null, + updatedAt: new Date(), + }).where(and( + eq(connectionGrants.id, grantId), + eq(connectionGrants.companyId, connection.companyId), + eq(connectionGrants.connectionId, connection.id), + )).returning(); + if (!updated) throw notFound("Connection grant not found"); + if (removedDelegations.length > 0) { + await tx.insert(toolAccessAuditEvents).values(removedDelegations.map((delegation) => ({ + companyId: connection.companyId, + connectionId: connection.id, + actorType: binding.actorType ?? "system", + actorId: binding.actorId, + action: "connection_grant.delegation_revoked", + outcome: "success", + reasonCode: "grant_revoked", + details: { grantId, delegationId: delegation.id, agentId: delegation.agentId }, + }))); + } + return updated; + }); if (!grant) throw notFound("Connection grant not found"); await db.insert(toolAccessAuditEvents).values({ companyId: connection.companyId, @@ -8238,6 +8816,7 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} transportConfig: isGoogleSheetsConnectionConfig(config) ? config : input.transportConfig ?? config, credentialRefs: input.credentialRefs ?? existing.credentialRefs, credentialSecretRefs: input.credentialSecretRefs ?? existing.credentialSecretRefs, + credentialPolicy: input.credentialPolicy ?? existing.credentialPolicy, updatedAt: new Date(), }) .where(eq(toolConnections.id, connectionId)) @@ -9040,47 +9619,100 @@ export function toolAccessService(db: Db, options: ToolAccessServiceOptions = {} ); } - const subject = input.body.subject ?? { type: "app" as const }; - if (subject.type === "user" && subject.userId !== runContext.responsibleUserId) { + const requestedSubject = input.body.subject; + if (requestedSubject?.type === "user" && requestedSubject.userId !== runContext.responsibleUserId) { await fail(403, "The agent run cannot act as the requested user", "denied", "subject_not_permitted", { connection: { uid: connection.uid }, - subject, + subject: requestedSubject, }); } - let grant: typeof connectionGrants.$inferSelect; - if (subject.type === "user") { - const conditions = [ + const actingUserId = runContext.responsibleUserId; + const autonomous = runContext.run.invocationSource === "automation" || runContext.run.invocationSource === "timer"; + const subject = connection.credentialPolicy === "shared" || !actingUserId + ? { type: "app" as const } + : { type: "user" as const, userId: actingUserId }; + let grant: typeof connectionGrants.$inferSelect | undefined; + if (connection.credentialPolicy !== "shared" && actingUserId) { + const [membership] = await db.select({ id: companyMemberships.id }).from(companyMemberships).where(and( + eq(companyMemberships.companyId, connection.companyId), + eq(companyMemberships.principalType, "user"), + eq(companyMemberships.principalId, actingUserId), + eq(companyMemberships.status, "active"), + )).limit(1); + if (!membership) { + await fail(403, "The personal grant owner is not an active company member", "denied", "grant_owner_membership_inactive", { + connection: { id: connection.id, uid: connection.uid, name: connection.name }, + subject: { type: "user", userId: actingUserId }, + remediation: { action: "restore_membership_or_reconnect" }, + }); + } + [grant] = await db.select().from(connectionGrants).where(and( eq(connectionGrants.companyId, connection.companyId), eq(connectionGrants.connectionId, connection.id), eq(connectionGrants.kind, "user"), - eq(connectionGrants.subjectUserId, subject.userId), - ]; - if (input.body.grantId) conditions.push(eq(connectionGrants.id, input.body.grantId)); - [grant] = await db.select().from(connectionGrants).where(and(...conditions)).limit(1); - if (!grant) { - await fail(409, "User authorization is required", "denied", "user_authorization_required", { - connection: { uid: connection.uid }, - subject, - remediation: { action: "start_authorization" }, - }); - } - } else if (input.body.grantId) { - [grant] = await db.select().from(connectionGrants).where(and( - eq(connectionGrants.id, input.body.grantId), - eq(connectionGrants.companyId, connection.companyId), - eq(connectionGrants.connectionId, connection.id), - eq(connectionGrants.kind, "workspace"), + eq(connectionGrants.subjectUserId, actingUserId), )).limit(1); - if (!grant) { - await fail(409, "The requested installation is not available", "denied", "installation_required", { - connection: { uid: connection.uid }, - subject, - remediation: { action: "add_installation" }, + } + if (!grant && connection.credentialPolicy === "per_user") { + await fail(409, "User authorization is required", "denied", "user_authorization_required", { + connection: { uid: connection.uid }, + subject: actingUserId ? { type: "user", userId: actingUserId } : { type: "app" }, + remediation: { action: "start_authorization" }, + }); + } + if (!grant) { + grant = await ensureDefaultOrganizationGrant(connection); + } + + if (grant.kind === "user" && autonomous) { + const [delegation] = await db.select({ id: connectionGrantDelegations.id }).from(connectionGrantDelegations).where(and( + eq(connectionGrantDelegations.companyId, connection.companyId), + eq(connectionGrantDelegations.grantId, grant.id), + eq(connectionGrantDelegations.agentId, input.agentId), + )).limit(1); + if (!delegation) { + if (actingUserId) { + await createStandingDelegationAsk({ + connection, + issueId: runContext.issueId, + runId: input.runId, + agentId: input.agentId, + ownerUserId: actingUserId, + }); + } + await fail(409, "Standing delegation is required for this autonomous run", "denied", "standing_delegation_required", { + connection: { id: connection.id, uid: connection.uid, name: connection.name }, + grantId: grant.id, + subject: actingUserId ? { type: "user", userId: actingUserId } : { type: "app" }, + agentId: input.agentId, + remediation: { action: "delegate_personal_grant", grantId: grant.id, agentId: input.agentId }, + }); + } + } + + if (grant.kind === "organization") { + const activeAudienceMember = actingUserId ? await db.select({ id: companyMemberships.id }).from(companyMemberships).where(and( + eq(companyMemberships.companyId, connection.companyId), + eq(companyMemberships.principalType, "user"), + eq(companyMemberships.principalId, actingUserId), + eq(companyMemberships.status, "active"), + )).limit(1).then((rows) => rows[0] ?? null) : null; + const audience = await db.select({ subjectId: connectionGrantMembers.subjectId }).from(connectionGrantMembers).where(and( + eq(connectionGrantMembers.companyId, connection.companyId), + eq(connectionGrantMembers.grantId, grant.id), + eq(connectionGrantMembers.subjectType, "user"), + )); + if ( + (actingUserId !== null && !activeAudienceMember) + || (audience.length > 0 && (!actingUserId || !audience.some((member) => member.subjectId === actingUserId))) + ) { + await fail(403, "The acting user is not in this grant's audience", "denied", "grant_audience_denied", { + connection: { uid: connection.uid }, + subject, + grantId: grant.id, }); } - } else { - grant = await ensureDefaultWorkspaceGrant(connection); } if (grant.status !== "active") { diff --git a/server/src/services/tool-gateway.ts b/server/src/services/tool-gateway.ts index 023317ed53..e470afe5ab 100644 --- a/server/src/services/tool-gateway.ts +++ b/server/src/services/tool-gateway.ts @@ -5,6 +5,11 @@ import type { Db } from "@paperclipai/db"; import { agents, approvals, + companies, + companyMemberships, + connectionGrantMembers, + connectionGrantDelegations, + connectionGrants, documents, heartbeatRuns, issueApprovals, @@ -80,6 +85,25 @@ import { const DEFAULT_SESSION_TTL_MS = 15 * 60 * 1000; const MAX_SESSION_TTL_MS = 60 * 60 * 1000; const DEFAULT_TOOL_TIMEOUT_MS = 10_000; + +export function resolveCredentialGrantKind( + policy: "shared" | "per_user" | "per_user_with_fallback", + actingUserId: string | null, + hasUserGrant: boolean, +): "organization" | "user" | "user_authorization_required" { + if (policy === "shared") return "organization"; + if (actingUserId && hasUserGrant) return "user"; + return policy === "per_user" ? "user_authorization_required" : "organization"; +} + +export function isConnectionGrantAudienceAllowed( + memberUserIds: string[], + actingUserId: string | null, + actingUserIsActiveMember: boolean, +): boolean { + if (actingUserId !== null && !actingUserIsActiveMember) return false; + return memberUserIds.length === 0 || (actingUserId !== null && memberUserIds.includes(actingUserId)); +} // When a human approves a parked write, the server carries it out on their // behalf with no interactive caller left to raise `timeoutMs`. Remote write // providers (e.g. Zapier Google Sheets `add_row`) routinely take longer than @@ -2389,16 +2413,36 @@ export function createToolGatewayService( .where(eq(toolConnections.id, connection.id)); } - async function resolveCredentialHeaders(connection: typeof toolConnections.$inferSelect): Promise> { + function grantRefForHeader( + grant: typeof connectionGrants.$inferSelect, + ref: McpConnectionCredentialRef, + ): ToolCredentialSecretRef | undefined { + return grant.credentialSecretRefs.find((candidate) => + candidate.configPath === ref.name || candidate.configPath === `credentials.${ref.name}`, + ); + } + + async function resolveCredentialHeaders( + session: ToolGatewaySession, + connection: typeof toolConnections.$inferSelect, + grant: typeof connectionGrants.$inferSelect, + ): Promise> { const headers: Record = {}; for (const ref of connection.credentialRefs ?? []) { if (ref.placement !== "header") continue; + const grantRef = grantRefForHeader(grant, ref); + if (!grantRef) continue; try { - const value = await secrets.resolveSecretValue(connection.companyId, ref.secretId, ref.version ?? "latest", { - consumerType: "tool_connection", - consumerId: connection.id, - configPath: `credentials.${ref.name}`, - actorType: "system", + const value = await secrets.resolveSecretValue(connection.companyId, grantRef.secretId, grantRef.versionSelector ?? "latest", { + accessContext: { + consumerType: "tool_connection", + consumerId: connection.id, + configPath: `credentials.${ref.name}`, + actorType: "system", + actorId: session.agentId, + issueId: session.issueId, + heartbeatRunId: session.runId, + }, }); headers[ref.key] = `${ref.prefix ?? ""}${value}`; } catch { @@ -2411,6 +2455,34 @@ export function createToolGatewayService( ); } } + const oauthAccessRef = grant.credentialSecretRefs.find((ref) => ref.configPath === "oauth.access_token"); + if (oauthAccessRef && headers.Authorization === undefined) { + try { + const value = await secrets.resolveSecretValue( + connection.companyId, + oauthAccessRef.secretId, + oauthAccessRef.versionSelector ?? "latest", + { + accessContext: { + consumerType: "tool_connection", + consumerId: connection.id, + configPath: oauthAccessRef.configPath, + actorType: "system", + actorId: session.agentId, + issueId: session.issueId, + heartbeatRunId: session.runId, + }, + }, + ); + headers.Authorization = `Bearer ${value}`; + } catch { + await markRemoteConnectionHealth(connection, "missing_secret", "A configured credential secret could not be resolved."); + throw new ToolGatewayHttpError(422, "A configured credential secret could not be resolved.", "mcp_remote_missing_secret", { + connectionId: connection.id, + credential: oauthAccessRef.configPath, + }); + } + } return headers; } @@ -2430,12 +2502,7 @@ export function createToolGatewayService( ): Promise { const versionSelector = input.versionSelector ?? "latest"; try { - const resolvedVersion = await secrets.resolveSecretVersion(connection.companyId, input.secretId, versionSelector, { - consumerType: "tool_connection", - consumerId: connection.id, - configPath: input.configPath, - actorType: "system", - }); + const resolvedVersion = await secrets.resolveSecretVersion(connection.companyId, input.secretId, versionSelector); return { refHash: input.refHash, versionSelector: String(versionSelector), @@ -2461,6 +2528,7 @@ export function createToolGatewayService( async function connectedCredentialVersionSnapshots( connection: typeof toolConnections.$inferSelect, + grant: typeof connectionGrants.$inferSelect, options: { requireResolved: boolean }, ): Promise<{ headerCredentialVersions: ConnectedCredentialVersionSnapshot[]; @@ -2472,15 +2540,17 @@ export function createToolGatewayService( for (const ref of connection.credentialRefs ?? []) { if (ref.placement !== "header") continue; const typedRef = ref as McpConnectionCredentialRef; + const grantRef = grantRefForHeader(grant, typedRef); + if (!grantRef) continue; const configPath = `credentials.${typedRef.name}`; headerCredentialVersions.push(await resolveConnectedCredentialVersion(connection, { - secretId: typedRef.secretId, - versionSelector: typedRef.version, + secretId: grantRef.secretId, + versionSelector: grantRef.versionSelector, configPath, refHash: credentialVersionRefHash({ kind: "header", name: typedRef.name, - secretId: typedRef.secretId, + secretId: grantRef.secretId, placement: typedRef.placement, key: typedRef.key, prefix: typedRef.prefix ?? null, @@ -2490,7 +2560,7 @@ export function createToolGatewayService( })); } - for (const ref of connection.credentialSecretRefs ?? []) { + for (const ref of grant.credentialSecretRefs ?? []) { const typedRef = ref as ToolCredentialSecretRef; credentialSecretVersions.push(await resolveConnectedCredentialVersion(connection, { secretId: typedRef.secretId, @@ -2510,6 +2580,252 @@ export function createToolGatewayService( return { headerCredentialVersions, credentialSecretVersions }; } + async function createUserAuthorizationInteraction( + session: ToolGatewaySession, + connection: typeof toolConnections.$inferSelect, + userId: string, + ) { + if (!session.issueId || !session.agentId || !session.runId) return; + const [company] = await db.select({ issuePrefix: companies.issuePrefix }).from(companies) + .where(eq(companies.id, session.companyId)).limit(1); + const href = `/${company?.issuePrefix ?? ""}/apps/${connection.id}/setup`; + const idempotencyKey = `connection-authorization:${connection.id}:${userId}`; + const payload = { + version: 1 as const, + prompt: `Connect your ${connection.name} account to continue`, + acceptLabel: "Connect account", + rejectLabel: "Not now", + detailsMarkdown: "This run needs your personal authorization. Paperclip will not use another user's identity.", + target: { + type: "custom" as const, + key: `connection:${connection.uid}:user:${userId}`, + revisionId: connection.updatedAt.toISOString(), + label: `Connect ${connection.name}`, + href, + }, + }; + const [existing] = await db.select({ id: issueThreadInteractions.id }).from(issueThreadInteractions).where(and( + eq(issueThreadInteractions.companyId, session.companyId), + eq(issueThreadInteractions.issueId, session.issueId), + eq(issueThreadInteractions.idempotencyKey, idempotencyKey), + )).limit(1); + if (existing) { + await db.update(issueThreadInteractions).set({ + status: "pending", + continuationPolicy: "wake_assignee", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + resolverPolicyProvenance: "explicit", + effectiveResolverPolicySource: "requested", + addresseeUserId: userId, + payload, + result: null, + resolvedAt: null, + updatedAt: new Date(), + }).where(eq(issueThreadInteractions.id, existing.id)); + return; + } + await db.insert(issueThreadInteractions).values({ + companyId: session.companyId, + issueId: session.issueId, + kind: "request_confirmation", + status: "pending", + continuationPolicy: "wake_assignee", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + resolverPolicyProvenance: "explicit", + effectiveResolverPolicySource: "requested", + idempotencyKey, + sourceRunId: session.runId, + title: `Connect your ${connection.name}`, + summary: "Personal authorization is required before this run can continue.", + createdByAgentId: session.agentId, + addresseeUserId: userId, + payload, + }); + } + + async function createStandingDelegationInteraction( + session: ToolGatewaySession, + connection: typeof toolConnections.$inferSelect, + userId: string, + ) { + if (!session.issueId || !session.agentId || !session.runId) return; + const [company] = await db.select({ issuePrefix: companies.issuePrefix }).from(companies) + .where(eq(companies.id, session.companyId)).limit(1); + const href = `/${company?.issuePrefix ?? ""}/apps/${connection.id}/setup`; + const idempotencyKey = `connection-delegation:${connection.id}:${userId}:${session.agentId}`; + const payload = { + version: 1 as const, + prompt: `Allow this agent to use your ${connection.name} account for autonomous runs`, + acceptLabel: "Review delegation", + rejectLabel: "Not now", + detailsMarkdown: "This autonomous run is paused. Paperclip will not use your personal identity until you explicitly delegate it to this named agent.", + target: { + type: "custom" as const, + key: `connection:${connection.uid}:delegation:${userId}:${session.agentId}`, + revisionId: connection.updatedAt.toISOString(), + label: `Delegate ${connection.name}`, + href, + }, + }; + const [existing] = await db.select({ id: issueThreadInteractions.id }).from(issueThreadInteractions).where(and( + eq(issueThreadInteractions.companyId, session.companyId), + eq(issueThreadInteractions.issueId, session.issueId), + eq(issueThreadInteractions.idempotencyKey, idempotencyKey), + )).limit(1); + if (existing) { + await db.update(issueThreadInteractions).set({ + status: "pending", + continuationPolicy: "wake_assignee", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + resolverPolicyProvenance: "explicit", + effectiveResolverPolicySource: "requested", + addresseeUserId: userId, + payload, + result: null, + resolvedAt: null, + updatedAt: new Date(), + }).where(eq(issueThreadInteractions.id, existing.id)); + return; + } + await db.insert(issueThreadInteractions).values({ + companyId: session.companyId, + issueId: session.issueId, + kind: "request_confirmation", + status: "pending", + continuationPolicy: "wake_assignee", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + resolverPolicyProvenance: "explicit", + effectiveResolverPolicySource: "requested", + idempotencyKey, + sourceRunId: session.runId, + title: `Delegate your ${connection.name}`, + summary: "An explicit standing delegation is required for this autonomous run.", + createdByAgentId: session.agentId, + addresseeUserId: userId, + payload, + }); + } + + async function resolveConnectionGrant( + session: ToolGatewaySession, + connection: typeof toolConnections.$inferSelect, + ): Promise { + const [run] = session.runId + ? await db.select({ + responsibleUserId: heartbeatRuns.responsibleUserId, + invocationSource: heartbeatRuns.invocationSource, + }).from(heartbeatRuns).where(and( + eq(heartbeatRuns.id, session.runId), + eq(heartbeatRuns.companyId, session.companyId), + )).limit(1) + : []; + const actingUserId = run?.responsibleUserId ?? null; + const autonomous = run?.invocationSource === "automation" || run?.invocationSource === "timer"; + const findUserGrant = async () => { + if (!actingUserId) return undefined; + const [membership] = await db.select({ id: companyMemberships.id }).from(companyMemberships).where(and( + eq(companyMemberships.companyId, connection.companyId), + eq(companyMemberships.principalType, "user"), + eq(companyMemberships.principalId, actingUserId), + eq(companyMemberships.status, "active"), + )).limit(1); + if (!membership) { + throw new ToolGatewayHttpError(403, "The personal grant owner is not an active company member", "grant_owner_membership_inactive", { + connectionId: connection.id, + actingUserId, + remediation: { action: "restore_membership_or_reconnect" }, + }); + } + const [grant] = await db.select().from(connectionGrants).where(and( + eq(connectionGrants.companyId, connection.companyId), + eq(connectionGrants.connectionId, connection.id), + eq(connectionGrants.kind, "user"), + eq(connectionGrants.subjectUserId, actingUserId), + eq(connectionGrants.status, "active"), + )).limit(1); + return grant; + }; + const findOrganizationGrant = async () => { + const [grant] = await db.select().from(connectionGrants).where(and( + eq(connectionGrants.companyId, connection.companyId), + eq(connectionGrants.connectionId, connection.id), + eq(connectionGrants.kind, "organization"), + eq(connectionGrants.isDefault, true), + eq(connectionGrants.status, "active"), + )).limit(1); + if (!grant) { + throw new ToolGatewayHttpError(409, "Organization authorization is required", "organization_authorization_required", { + connectionId: connection.id, + }); + } + const members = await db.select({ subjectId: connectionGrantMembers.subjectId }).from(connectionGrantMembers).where(and( + eq(connectionGrantMembers.companyId, connection.companyId), + eq(connectionGrantMembers.grantId, grant.id), + eq(connectionGrantMembers.subjectType, "user"), + )); + const activeAudienceMember = actingUserId ? await db.select({ id: companyMemberships.id }).from(companyMemberships).where(and( + eq(companyMemberships.companyId, connection.companyId), + eq(companyMemberships.principalType, "user"), + eq(companyMemberships.principalId, actingUserId), + eq(companyMemberships.status, "active"), + )).limit(1).then((rows) => rows[0] ?? null) : null; + if (!isConnectionGrantAudienceAllowed( + members.map((member) => member.subjectId), + actingUserId, + Boolean(activeAudienceMember), + )) { + throw new ToolGatewayHttpError(403, "The acting user is not in this grant's audience", "grant_audience_denied", { + connectionId: connection.id, + grantId: grant.id, + actingUserId, + }); + } + return grant; + }; + + const userGrant = connection.credentialPolicy === "shared" ? undefined : await findUserGrant(); + const resolution = resolveCredentialGrantKind(connection.credentialPolicy, actingUserId, Boolean(userGrant)); + if (resolution === "user" && userGrant) { + if (autonomous) { + if (!session.agentId) { + throw new ToolGatewayHttpError(409, "Standing delegation requires a named agent", "standing_delegation_required", { + connectionId: connection.id, + grantId: userGrant.id, + actingUserId, + }); + } + const [delegation] = await db.select({ id: connectionGrantDelegations.id }).from(connectionGrantDelegations).where(and( + eq(connectionGrantDelegations.companyId, connection.companyId), + eq(connectionGrantDelegations.grantId, userGrant.id), + eq(connectionGrantDelegations.agentId, session.agentId), + )).limit(1); + if (!delegation) { + await createStandingDelegationInteraction(session, connection, actingUserId!); + throw new ToolGatewayHttpError(409, "Standing delegation is required for this autonomous run", "standing_delegation_required", { + connectionId: connection.id, + grantId: userGrant.id, + actingUserId, + agentId: session.agentId, + remediation: { action: "delegate_personal_grant", grantId: userGrant.id, agentId: session.agentId }, + }); + } + } + return userGrant; + } + if (resolution === "user_authorization_required") { + if (actingUserId) await createUserAuthorizationInteraction(session, connection, actingUserId); + throw new ToolGatewayHttpError(409, "User authorization is required", "user_authorization_required", { + connectionId: connection.id, + actingUserId, + }); + } + return findOrganizationGrant(); + } + async function resolveConnectedRemoteTool(session: ToolGatewaySession, tool: ToolGatewayDescriptor) { if (tool.providerType !== "mcp_remote_http" || !tool.connectionId || !tool.catalogEntryId) { throw new ToolGatewayHttpError(404, `Tool "${tool.name}" not found`, "tool_not_found"); @@ -2615,9 +2931,12 @@ export function createToolGatewayService( }; } - function localStdioEnvironment(connection: typeof toolConnections.$inferSelect, template: LocalStdioRuntimeTemplate): NodeJS.ProcessEnv { - const config = asRecord(connection.config) ?? {}; - const configEnv = asRecord(config.env) ?? {}; + async function localStdioEnvironment( + session: ToolGatewaySession, + connection: typeof toolConnections.$inferSelect, + template: LocalStdioRuntimeTemplate, + grant: typeof connectionGrants.$inferSelect, + ): Promise { const env: NodeJS.ProcessEnv = {}; for (const key of ["PATH", "Path", "SystemRoot", "WINDIR", "COMSPEC", "PATHEXT"]) { const value = process.env[key]; @@ -2626,9 +2945,33 @@ export function createToolGatewayService( } } for (const key of template.envKeys) { - const configured = configEnv[key]; - if (typeof configured === "string") { - env[key] = configured; + const grantRef = grant.credentialSecretRefs.find((ref) => ref.configPath === `env.${key}`); + if (!grantRef) continue; + try { + env[key] = await secrets.resolveSecretValue( + connection.companyId, + grantRef.secretId, + grantRef.versionSelector ?? "latest", + { + accessContext: { + consumerType: "tool_connection", + consumerId: connection.id, + configPath: grantRef.configPath, + actorType: "system", + actorId: session.agentId, + issueId: session.issueId, + heartbeatRunId: session.runId, + }, + }, + ); + } catch { + await markRemoteConnectionHealth(connection, "missing_secret", "A configured local stdio credential could not be resolved."); + throw new ToolGatewayHttpError( + 422, + "A configured local stdio credential could not be resolved.", + "local_stdio_missing_secret", + { connectionId: connection.id, credential: grantRef.configPath }, + ); } } return env; @@ -2642,6 +2985,7 @@ export function createToolGatewayService( connection: typeof toolConnections.$inferSelect; entry: typeof toolCatalogEntries.$inferSelect; template: LocalStdioRuntimeTemplate; + env: NodeJS.ProcessEnv; parameters: unknown; timeoutMs: number; }): Promise { @@ -2654,7 +2998,7 @@ export function createToolGatewayService( ); } const child = spawn(input.template.command, input.template.args, { - env: localStdioEnvironment(input.connection, input.template), + env: input.env, stdio: ["pipe", "pipe", "pipe"], }); let stdout = ""; @@ -2801,7 +3145,8 @@ export function createToolGatewayService( )) .limit(1); if (!row) return null; - const credentialVersions = await connectedCredentialVersionSnapshots(row.connection, { + const grant = await resolveConnectionGrant(session, row.connection); + const credentialVersions = await connectedCredentialVersionSnapshots(row.connection, grant, { requireResolved: options.requireResolvedCredentials === true, }); return { @@ -2817,6 +3162,8 @@ export function createToolGatewayService( connectionTransportConfigHash: stableHash(row.connection.transportConfig ?? {}), credentialRefsHash: stableHash(row.connection.credentialRefs ?? []), credentialSecretRefsHash: stableHash(row.connection.credentialSecretRefs ?? []), + credentialGrantId: grant.id, + credentialGrantRefsHash: stableHash(grant.credentialSecretRefs ?? []), headerCredentialVersions: credentialVersions.headerCredentialVersions, credentialSecretVersions: credentialVersions.credentialSecretVersions, catalogEntryId: row.entry.id, @@ -3058,13 +3405,14 @@ export function createToolGatewayService( callerHeaders?: ExecuteGatewayToolInput["callerHeaders"], ): Promise { const { entry, connection } = await resolveConnectedRemoteTool(session, tool); + const grant = await resolveConnectionGrant(session, connection); const endpoint = remoteEndpoint(connection.config ?? {}); // Method-defined headers are trusted catalog configuration. Treat them as // managed headers so callers cannot override the scope that was reviewed // during tools/list. Credentials remain authoritative on collisions. const credentialHeaders = { ...projectedConnectionHeaders(connection), - ...await resolveCredentialHeaders(connection), + ...await resolveCredentialHeaders(session, connection, grant), }; const { headers, summary: headerSummary } = buildRemoteHeaders({ session, @@ -3208,7 +3556,9 @@ export function createToolGatewayService( ms: number, ): Promise { const { entry, connection } = await resolveConnectedLocalStdioTool(session, tool); + const grant = await resolveConnectionGrant(session, connection); const template = await resolveLocalStdioRuntimeTemplate(connection); + const env = await localStdioEnvironment(session, connection, template, grant); const result = await runtimeSupervisor.useConnectionSlot( { companyId: session.companyId, @@ -3232,6 +3582,7 @@ export function createToolGatewayService( connection, entry, template, + env, parameters, timeoutMs: ms, }); diff --git a/tests/e2e/app-not-connected.spec.ts b/tests/e2e/app-not-connected.spec.ts index 0db1b01905..9eaa9be013 100644 --- a/tests/e2e/app-not-connected.spec.ts +++ b/tests/e2e/app-not-connected.spec.ts @@ -123,10 +123,6 @@ test.describe.serial("not-connected app page", () => { await page.screenshot({ path: `${SCREENSHOT_DIR}/apps-nav-w6-02-reconnect-prefilled.png`, fullPage: true }); await page.getByRole("button", { name: "Check link" }).click(); - await expect(page.getByRole("heading", { name: "Who can use Bla?" })).toBeVisible({ timeout: 30_000 }); - await page.getByRole("button", { name: "Continue to install" }).click(); - await expect(page.getByRole("heading", { name: /Install .* tools\?/i })).toBeVisible({ timeout: 20_000 }); - await page.getByRole("button", { name: "Finish setup" }).click(); await expect(page.getByRole("heading", { name: "Bla is ready." })).toBeVisible({ timeout: 20_000 }); const apps = await request.get(`/api/companies/${seed.companyId}/tools/applications`); @@ -147,37 +143,25 @@ test.describe.serial("not-connected app page", () => { expect(appConns[0].status).not.toBe("archived"); }); - test("draft app connection stays on provider setup until setup finishes", async ({ page, request }) => { - const draftMock = await startMockMcp(); - try { - const draft = await request.post(`/api/companies/${seed.companyId}/tools/apps/connect`, { - data: { - link: draftMock.url, - name: "Draft app", - credentialValues: { "credentials.authorization": "qa-token" }, - }, - }); - expect(draft.ok(), `draft connect failed ${draft.status()}: ${await draft.text()}`).toBe(true); - const draftBody = await draft.json(); - const draftApplicationId = draftBody.application.id as string; - const archive = await request.delete(`/api/tool-connections/${draftBody.connectionId}`); - expect(archive.ok(), `draft archive failed ${archive.status()}: ${await archive.text()}`).toBe(true); - const revive = await request.patch(`/api/tool-applications/${draftApplicationId}`, { data: { status: "active" } }); - expect(revive.ok(), `draft revive failed ${revive.status()}: ${await revive.text()}`).toBe(true); + test("archived app connection returns to provider setup", async ({ page, request }) => { + const archive = await request.delete(`/api/tool-connections/${connectionId}`); + expect(archive.ok(), `archive failed ${archive.status()}: ${await archive.text()}`).toBe(true); + const revive = await request.patch(`/api/tool-applications/${applicationId}`, { data: { status: "active" } }); + expect(revive.ok(), `revive failed ${revive.status()}: ${await revive.text()}`).toBe(true); - await page.goto(`/${seed.prefix}/apps/app/${draftApplicationId}`); - await expect(page).toHaveURL(new RegExp(`/${seed.prefix}/apps/app/${draftApplicationId}/setup$`), { timeout: 20_000 }); - await expect(page.getByText("Not connected", { exact: true })).toBeVisible(); - await expect(page.getByRole("heading", { name: "Connect this app" })).toBeVisible(); + await page.goto(`/${seed.prefix}/apps/app/${applicationId}`); + await expect(page).toHaveURL( + new RegExp(`/${seed.prefix}/apps/app/${applicationId}/setup$`), + { timeout: 20_000 }, + ); + await expect(page.getByText("Not connected", { exact: true })).toBeVisible(); + await expect(page.getByRole("heading", { name: "Connect this app" })).toBeVisible(); - await page.goto(`/${seed.prefix}/apps/connections`); - const row = page.locator("tbody tr", { hasText: "Draft app" }); - await expect(row).toBeVisible({ timeout: 30_000 }); - await expect(row.getByRole("button", { name: "Connect" })).toBeVisible(); - await page.screenshot({ path: `${SCREENSHOT_DIR}/apps-nav-w6-03-reconnected-row.png`, fullPage: true }); - } finally { - await draftMock.close(); - } + await page.goto(`/${seed.prefix}/apps/connections`); + const row = page.locator("tbody tr", { hasText: "Bla" }); + await expect(row).toBeVisible({ timeout: 30_000 }); + await expect(row.getByRole("button", { name: "Connect" })).toBeVisible(); + await page.screenshot({ path: `${SCREENSHOT_DIR}/apps-nav-w6-03-reconnected-row.png`, fullPage: true }); }); test("danger zone on the app page removes the app", async ({ page, request }) => { diff --git a/tests/e2e/applications-crud.spec.ts b/tests/e2e/applications-crud.spec.ts index da059b6181..db89eb6239 100644 --- a/tests/e2e/applications-crud.spec.ts +++ b/tests/e2e/applications-crud.spec.ts @@ -75,7 +75,7 @@ test.describe.serial("applications lifecycle", () => { test("Connections list surfaces connected and not-connected apps", async ({ page, request }) => { const connectedName = `${APP_PREFIX}-connected`; - const notConnectedName = `${APP_PREFIX}-not-connected`; + const notConnectedName = `${APP_PREFIX}-offline`; const connected = await createConnection(request, seed.companyId, { applicationName: connectedName, name: connectedName, diff --git a/tests/e2e/apps-dark-mode-shots.spec.ts b/tests/e2e/apps-dark-mode-shots.spec.ts index d43a4bd38a..c8eb1199ef 100644 --- a/tests/e2e/apps-dark-mode-shots.spec.ts +++ b/tests/e2e/apps-dark-mode-shots.spec.ts @@ -161,6 +161,8 @@ test.describe.serial("dark-mode Apps surfaces", () => { await forceDark(page); await page.goto(`/${seed.prefix}/apps/advanced/profiles`); await expect(page.getByRole("heading", { name: "Access profiles" })).toBeVisible({ timeout: 30_000 }); + await expect(page.locator('a[href$="/apps/advanced/gateways"]', { hasText: "Gateways" })).toBeVisible(); + await expect(page.locator('a[href$="/apps/advanced/profiles"]', { hasText: "Profiles" })).toBeVisible(); await expect(page.locator('a[href$="/apps/advanced/audit"]', { hasText: "Activity" })).toBeVisible(); await expect(page.getByRole("link", { name: "Applications", exact: true })).toHaveCount(0); // Apps section lives in the same sidebar now. diff --git a/tests/e2e/apps-prosumer-mcp-flow.spec.ts b/tests/e2e/apps-prosumer-mcp-flow.spec.ts index 4935f0d885..ea663c476b 100644 --- a/tests/e2e/apps-prosumer-mcp-flow.spec.ts +++ b/tests/e2e/apps-prosumer-mcp-flow.spec.ts @@ -4,7 +4,7 @@ import { listenOnFetchAllowedPort } from "./fetch-allowed-port"; // prosumer MCP flow — QA harness for the prosumer Connect-an-app flow on top of the // tool-access foundation. Covers the M-series happy path (gallery + key paste -// → choose actions → who-can-use → success), the expired-key reconnect path, +// → choose access → install → success), the expired-key reconnect path, // the Needs-attention surface, and a regression check that /apps/advanced // still mounts. // @@ -32,8 +32,7 @@ async function newCompany(request: APIRequestContext, label: string): Promise Promise; captures: Array<{ method: string; params: unknown }> }; @@ -145,7 +144,7 @@ test.describe.serial("prosumer MCP flow prosumer MCP flow", () => { await mock?.close(); }); - test("Connect wizard happy path: link mode → access → install → success", async ({ page, request }) => { + test("Connect wizard happy path: link mode → success", async ({ page, request }) => { const seed = await newCompany(request, "connect"); await gotoConnect(page, seed.prefix); @@ -159,7 +158,7 @@ test.describe.serial("prosumer MCP flow prosumer MCP flow", () => { await linkInput.fill(mock.url); await page.getByRole("button", { name: "Continue" }).click(); - // LinkKey step shows the guided MCP connection heading. Mock doesn't + // LinkKey step keeps the BYO connection heading. Mock doesn't // require a key — leave the default "No" answer. await expect(page.getByRole("heading", { name: "Connect your own MCP server" })).toBeVisible({ timeout: 15_000 }); await page.screenshot({ path: `${SCREENSHOT_DIR}/prosumer-mcp-02-key-step.png`, fullPage: true }); @@ -167,19 +166,10 @@ test.describe.serial("prosumer MCP flow prosumer MCP flow", () => { // Submit (button label is "Check link"). await page.getByRole("button", { name: /Check link/i }).click(); - // Who-can-use step — defaults to All agents. - await expect(page.getByRole("heading", { name: /Who can use/i })).toBeVisible({ timeout: 30_000 }); - await page.screenshot({ path: `${SCREENSHOT_DIR}/prosumer-mcp-03-who-step.png`, fullPage: true }); - - await page.getByRole("button", { name: /Continue to install/i }).click(); - await expect(page.getByRole("heading", { name: /Install .* tools\?/i })).toBeVisible({ timeout: 15_000 }); - await page.screenshot({ path: `${SCREENSHOT_DIR}/prosumer-mcp-04-install-step.png`, fullPage: true }); - - // Finish. - await page.getByRole("button", { name: /Finish setup/i }).click(); - - // Success step. - await expect(page.getByText(/ready|all set|done/i).first()).toBeVisible({ timeout: 20_000 }); + // Link-mode setup uses the safe organization/any-agent defaults, enables + // discovered actions, and applies risk-based ask-first defaults in one + // commit. Classification remains covered by the server suite. + await expect(page.getByRole("heading", { name: /is ready\.$/i })).toBeVisible({ timeout: 30_000 }); await page.screenshot({ path: `${SCREENSHOT_DIR}/prosumer-mcp-05-success.png`, fullPage: true }); // Verify the mock saw a tools/list call from the catalog refresh. diff --git a/tests/e2e/mcp-user-stories.spec.ts b/tests/e2e/mcp-user-stories.spec.ts index 7e5d4600cd..6314a32d90 100644 --- a/tests/e2e/mcp-user-stories.spec.ts +++ b/tests/e2e/mcp-user-stories.spec.ts @@ -95,6 +95,7 @@ async function setScoutScript( ) { await json(await request.patch(`/api/agents/${scout.id}`, { data: { + adapterType: "process", adapterConfig: { command: process.execPath, args: ["--input-type=module", "-e", script], @@ -287,17 +288,22 @@ test.describe.serial("MCP prod Phase 5a user-story harness", () => { test(`${storyById("US-1").id} ${storyById("US-1").title} @mcp-runnable @mcp-us1`, async ({ page, request }) => { const { seed, scout, mock, connectionId } = await seedConnectedFixture(request, "us1"); try { - await page.goto(`/${seed.prefix}/apps/${connectionId}`); - await expect(page.getByRole("heading", { name: /Sheets Fixture us1/i })).toBeVisible({ timeout: 30_000 }); - await screenshot(page, "US-1", "01-connected-app"); - await setScoutScript(request, scout, buildGatewayCallScript(connectionId, "sheets:list_rows")); const invoked = await invokeHeartbeat(request, scout.id); const run = await waitForRun(request, invoked.id); - expect(run.status, run.error ?? `heartbeat run ${run.id} did not succeed`).toBe("succeeded"); + const failedLog = run.status === "succeeded" + ? null + : await json<{ content?: string }>(await request.get(`/api/heartbeat-runs/${run.id}/log?offset=0&limitBytes=65536`)); + expect( + run.status, + [run.error ?? `heartbeat run ${run.id} did not succeed`, failedLog?.content].filter(Boolean).join("\n"), + ).toBe("succeeded"); expect(mock.captures.some((capture) => capture.method === "tools/call" && capture.toolName === "sheets:list_rows")).toBe(true); await expectAuditEvent(request, seed.companyId, { connectionId, agentId: scout.id, search: "sheets:list_rows" }); + await page.goto(`/${seed.prefix}/apps/${connectionId}`); + await expect(page.getByRole("heading", { name: /Sheets Fixture us1/i })).toBeVisible({ timeout: 30_000 }); + await screenshot(page, "US-1", "01-connected-app"); await page.goto(`/${seed.prefix}/apps/${connectionId}/activity`); await screenshot(page, "US-1", "02-activity"); } finally { diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 8fc3beae1f..09ce17617e 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -70,6 +70,7 @@ export default defineConfig({ ...process.env, NODE_ENV: "test", PORT: String(PORT), + PAPERCLIP_API_URL: BASE_URL, PAPERCLIP_HOME, PAPERCLIP_INSTANCE_ID, PAPERCLIP_CONFIG, diff --git a/ui/src/api/tools.ts b/ui/src/api/tools.ts index a6bc260e4c..52b397e9cf 100644 --- a/ui/src/api/tools.ts +++ b/ui/src/api/tools.ts @@ -53,6 +53,10 @@ import type { UpdateToolMcpGateway, CreateToolTrustRuleFromActionRequest, ToolRedactedValueSummary, + ConnectionGrant, + ConnectionGrantDelegation, + ConnectionGrantsResponse, + ToolConnectionCreateCapabilities, } from "@paperclipai/shared"; import { api } from "./client"; @@ -73,7 +77,10 @@ export type ToolRuntimeHealthResponse = ToolRuntimeHealthSummary; export type ToolTrustRulesResponse = { trustRules: ToolPolicy[] }; export type ToolPoliciesResponse = { policies: ToolPolicy[] }; export type ToolProfilesResponse = { profiles: ToolProfileWithDetails[] }; -export type ToolGalleryResponse = { apps: AppDefinition[] }; +export type ToolGalleryResponse = { + apps: AppDefinition[]; + capabilities: ToolConnectionCreateCapabilities; +}; export type ToolMcpGatewaysResponse = { gateways: ToolMcpGatewayWithTokens[] }; export type CreateGatewayTokenInput = Omit & { expiresAt?: string | Date | null; @@ -306,6 +313,46 @@ export const toolsApi = { `/tool-connections/${connectionId}/installs`, { installs }, ), + // --- Identity grants (PAP-17835): who a connection acts as. The response + // carries server-computed capabilities and the audience member directory, so + // the UI never rebuilds the permission matrix from `membershipRole`. + listConnectionGrants: (connectionId: string) => + api.get(`/tool-connections/${connectionId}/grants`), + createConnectionGrantDelegation: (connectionId: string, grantId: string, agentId: string) => + api.post( + `/tool-connections/${connectionId}/grants/${grantId}/delegations`, + { agentId }, + ), + revokeConnectionGrantDelegation: ( + connectionId: string, + grantId: string, + delegationId: string, + ) => api.delete( + `/tool-connections/${connectionId}/grants/${grantId}/delegations/${delegationId}`, + ), + revokeConnectionGrant: (connectionId: string, grantId: string) => + api.delete(`/tool-connections/${connectionId}/grants/${grantId}`), + // An empty `memberUserIds` is the canonical "all organization members". + // Replacement is atomic server-side, so this never partially widens access. + replaceConnectionGrantMembers: (connectionId: string, grantId: string, memberUserIds: string[]) => + api.put( + `/tool-connections/${connectionId}/grants/${grantId}/members`, + { memberUserIds }, + ), + /** + * Start the signed-in user's own personal authorization. `subjectUserId` must + * be the caller: the server refuses any other subject, so there is no way to + * initiate consent on someone else's behalf. + */ + startPersonalAuthorization: ( + companyId: string, + connectionId: string, + input: { subjectUserId: string; scopes?: string[]; returnTo?: string }, + ) => + api.post<{ url: string }>( + `/companies/${companyId}/tools/connections/${connectionId}/start-authorization`, + input, + ), createConnection: (companyId: string, input: CreateToolConnectionInput) => api.post(`/companies/${companyId}/tools/connections`, input), updateConnection: (connectionId: string, input: UpdateToolConnectionInput) => diff --git a/ui/src/components/IssueThreadInteractionCard.test.tsx b/ui/src/components/IssueThreadInteractionCard.test.tsx index 2b27093e8f..90d3b0cec4 100644 --- a/ui/src/components/IssueThreadInteractionCard.test.tsx +++ b/ui/src/components/IssueThreadInteractionCard.test.tsx @@ -23,7 +23,10 @@ import { pendingRequestConfirmationInteraction, pendingToolActionDestructiveInteraction, pendingToolActionWriteInteraction, + issueThreadInteractionFixtureMeta, pendingSecretProposalInteraction, + pendingConnectionAuthorizationInteraction, + resolvedConnectionAuthorizationInteraction, executedSecretProposalInteraction, failedSecretProposalInteraction, rejectedSecretProposalInteraction, @@ -1217,9 +1220,132 @@ describe("IssueThreadInteractionCard secret-proposal card", () => { }); /** - * The effective audience is shown *before* anyone responds, so a reader never - * has to guess whether an open card is waiting on them (PAP-17280). + * Connection authorization has its own card composition (PAP-17796 Surface F, + * corrected in PAP-17859). It must not fall through to the generic + * Approve / Revise… / Reject layout: there is nothing to revise, and only the + * addressed person can answer at all. */ +describe("IssueThreadInteractionCard connection-authorization card", () => { + const CAROL_LABELS = new Map([ + [issueThreadInteractionFixtureMeta.currentUserId, "Carol"], + ]); + + function buttonLabels(host: HTMLElement) { + return Array.from(host.querySelectorAll("button")).map((button) => button.textContent?.trim()); + } + + it("offers the addressed person Connect plus Not now, and nothing from the generic grammar", () => { + const host = renderCard({ + interaction: pendingConnectionAuthorizationInteraction, + currentUserId: issueThreadInteractionFixtureMeta.currentUserId, + onAcceptInteraction: async () => {}, + onRejectInteraction: async () => {}, + }); + + expect(host.textContent).toContain("Connect your Gmail to continue"); + + // "Action required" rather than the generic kind label: the mechanism is + // not the point, the blocked work is. + const statusBadge = host.querySelector('[data-testid="interaction-status-badge"]'); + expect(statusBadge?.textContent).toContain("Action required"); + expect(statusBadge?.textContent).not.toContain("Confirmation"); + + // One title, one body. The generic layout rendered `payload.prompt` too, + // which is the title verbatim. + const titles = (host.textContent ?? "").split("Connect your Gmail to continue").length - 1; + expect(titles).toBe(1); + const body = host.querySelector('[data-testid="connection-authorization-body"]'); + expect(host.querySelectorAll('[data-testid="connection-authorization-body"]').length).toBe(1); + expect(body?.textContent).toContain( + "Outreach Agent needs your Gmail identity for work running as you.", + ); + expect(body?.textContent).toContain("No one else can complete this step."); + + // The primary action is a link to the server-minted target, not an accept + // call: the OAuth callback is what resolves this card. + const connect = Array.from(host.querySelectorAll("a")).find((anchor) => + anchor.textContent?.includes("Connect Gmail"), + ); + expect(connect?.getAttribute("href")).toBe( + "https://accounts.google.com/o/oauth2/v2/auth?client_id=paperclip", + ); + expect(connect?.getAttribute("target")).toBe("_blank"); + + const labels = buttonLabels(host); + expect(labels).toContain("Not now"); + for (const generic of ["Approve", "Revise…", "Reject", "Send revision"]) { + expect(labels).not.toContain(generic); + } + + // Consent is the addressed person's alone. The card must never imply a + // teammate can give it for them. + expect(host.textContent?.toLowerCase()).not.toContain("on behalf of"); + expect(host.textContent?.toLowerCase()).not.toContain("anyone can"); + }); + + it("gives another reader Waiting for Carol and no action controls at all", () => { + const host = renderCard({ + interaction: pendingConnectionAuthorizationInteraction, + currentUserId: "user-someone-else", + userLabelMap: CAROL_LABELS, + onAcceptInteraction: async () => {}, + onRejectInteraction: async () => {}, + }); + + expect(host.querySelector('[data-testid="connection-authorization-waiting"]')?.textContent) + .toContain("Waiting for Carol"); + expect(host.querySelector('[data-testid="interaction-status-badge"]')?.textContent) + .toContain("Waiting for Carol"); + // The server's summary is second-person ("your Gmail identity", "as you") + // because it addressed one person. A teammate must not be told the agent + // wants *their* account. + const body = host.querySelector('[data-testid="connection-authorization-body"]')?.textContent; + expect(body).toBe( + "Outreach Agent needs Carol's Gmail identity for work running as them.", + ); + expect(host.querySelector('[data-testid="connection-authorization-waiting"]')?.textContent) + .toContain("Only Carol can connect their own Gmail account."); + + // Omitted, not disabled — and the authorization URL is not even in the DOM + // for someone who may not use it. + const labels = buttonLabels(host); + for (const forbidden of ["Connect Gmail", "Not now", "Approve", "Revise…", "Reject"]) { + expect(labels).not.toContain(forbidden); + } + expect( + Array.from(host.querySelectorAll("a")).some((a) => a.textContent?.includes("Connect Gmail")), + ).toBe(false); + expect(host.innerHTML).not.toContain("accounts.google.com"); + }); + + it("resolves to Gmail connected with the resolver and a timestamp", () => { + const host = renderCard({ + interaction: resolvedConnectionAuthorizationInteraction, + currentUserId: "user-someone-else", + userLabelMap: CAROL_LABELS, + onAcceptInteraction: async () => {}, + onRejectInteraction: async () => {}, + }); + + const statusBadge = host.querySelector('[data-testid="interaction-status-badge"]'); + expect(statusBadge?.textContent).toContain("Gmail connected"); + expect(statusBadge?.textContent).not.toContain("Action required"); + + const connected = host.querySelector('[data-testid="connection-authorization-connected"]'); + expect(connected?.textContent).toContain("Gmail connected"); + expect(connected?.textContent).toContain("Connected by Carol"); + expect(connected?.textContent).toMatch(/on .*2026/); + expect(host.querySelector('[data-testid="connection-authorization-body"]')?.textContent) + .toBe("Outreach Agent needed Carol's Gmail identity for work running as them."); + + // A resolved card never re-offers the spent authorization target. + expect(host.innerHTML).not.toContain("accounts.google.com"); + expect(buttonLabels(host)).not.toContain("Connect Gmail"); + // The card states its own resolver, so the shared footer must not repeat it. + expect(host.querySelector('[data-testid="interaction-resolved-footer"]')).toBeNull(); + }); +}); + describe("IssueThreadInteractionCard resolver audience", () => { it("shows an open audience on a pending card created without a restriction", () => { const host = renderCard({ interaction: pendingRequestConfirmationInteraction }); diff --git a/ui/src/components/IssueThreadInteractionCard.tsx b/ui/src/components/IssueThreadInteractionCard.tsx index a7b7f2ac2a..258f2e92bb 100644 --- a/ui/src/components/IssueThreadInteractionCard.tsx +++ b/ui/src/components/IssueThreadInteractionCard.tsx @@ -342,6 +342,86 @@ function isSecretProposalConfirmation(interaction: IssueThreadInteraction): bool return secretProposalPayload(interaction) != null; } +/** + * A `request_confirmation` carrying `payload.connectionAuthorization` is asking + * one person to connect their own account so an agent can act as them + * (PAP-17835). It keeps the interaction kind and the server-addressed audience; + * only the presentation differs, and it reads that presentation from the payload + * rather than parsing the title string. + */ +function connectionAuthorizationPayload( + interaction: IssueThreadInteraction, +): NonNullable | null { + if (interaction.kind !== "request_confirmation") return null; + return interaction.payload.connectionAuthorization ?? null; +} + +/** + * The five states the connection-authorization card can be read in (PAP-17859). + * + * `actionable` and `waiting` are the *same* pending row seen by two different + * readers: consent belongs to the addressed person alone, so who is looking + * changes what may be offered — not merely whether a button is greyed out. + */ +type ConnectionAuthorizationCardState = + | "actionable" + | "waiting" + | "connected" + | "declined" + | "expired"; + +function connectionAuthorizationCardState({ + interaction, + isAddressee, +}: { + interaction: RequestConfirmationInteraction; + isAddressee: boolean; +}): ConnectionAuthorizationCardState { + if (interaction.status === "accepted") return "connected"; + if (interaction.status === "rejected") return "declined"; + if (interaction.status === "pending") return isAddressee ? "actionable" : "waiting"; + // expired / cancelled / failed all mean the same thing to a reader here: the + // authorization run this card carried is over. + return "expired"; +} + +/** + * True only when the signed-in reader *is* the person the server addressed. + * + * Deliberately strict: an unknown viewer (`currentUserId` not loaded yet) is + * never treated as the addressee, so the Connect action cannot flash into view + * for someone who may not consent. The server re-authorizes the callback + * regardless; this only decides what the card offers. + */ +function isConnectionAuthorizationAddressee({ + interaction, + currentUserId, +}: { + interaction: RequestConfirmationInteraction; + currentUserId?: string | null; +}): boolean { + const addressee = interaction.addresseeUserId; + if (!addressee || !currentUserId) return false; + return addressee === currentUserId; +} + +/** + * The authorization URL this card may open, or `null`. + * + * A target is offered **only while the interaction is pending**. The server + * re-upserts this row with a freshly minted `state` every time it starts a new + * authorization run, so a pending card always carries a live target and a + * resolved/declined/expired one always carries a spent one. Gating on the + * status is therefore how "never reuse an expired OAuth URL" is enforced — + * there is no client-visible expiry on the payload to check instead. + */ +function connectionAuthorizationHref(interaction: RequestConfirmationInteraction): string | null { + if (interaction.status !== "pending") return null; + const href = interaction.payload.target?.href; + if (!href) return null; + return normalizeRequestConfirmationTargetHref(href); +} + type ToolActionCardState = | "pending" | "running" @@ -2482,6 +2562,262 @@ function ConfirmationActionRow({ ); } +function connectionAuthorizationStatusClasses( + state: ConnectionAuthorizationCardState, + copy: { providerName: string; addresseeLabel: string }, +): { + shell: string; + badge: string; + label: string; + Icon: typeof CheckCircle2; +} { + switch (state) { + case "actionable": + return { + shell: "border-2 border-sky-500/70 bg-transparent", + badge: "border-sky-500/60 bg-sky-500/10 text-sky-900 dark:bg-sky-500/15 dark:text-sky-100", + label: "Action required", + Icon: KeyRound, + }; + case "waiting": + // Not a warning and not a failure: someone else's decision is simply + // outstanding. The calm inert lane keeps it out of the reader's queue. + return { + shell: "border-border bg-transparent", + badge: "border-border bg-muted/60 text-muted-foreground", + label: `Waiting for ${copy.addresseeLabel}`, + Icon: Clock, + }; + case "connected": + return { + shell: "border-2 border-green-500/80 bg-transparent", + badge: "border-green-500/60 bg-green-500/10 text-green-900 dark:bg-green-500/15 dark:text-green-100", + label: `${copy.providerName} connected`, + Icon: CheckCircle2, + }; + case "declined": + return { + shell: "border-border bg-transparent", + badge: "border-border bg-muted/60 text-muted-foreground", + label: "Not connected", + Icon: MinusCircle, + }; + case "expired": + default: + return { + shell: "border-border bg-transparent", + badge: "border-border bg-muted/60 text-muted-foreground", + label: "Authorization expired", + Icon: CircleDashed, + }; + } +} + +/** + * Connection authorization — "Connect your Gmail to continue" (PAP-17796 + * Surface F, corrected in PAP-17859). + * + * This is deliberately *not* the generic Approve / Revise… / Reject grammar it + * used to fall through to. Authorization is not a review: there is nothing to + * revise, "Reject" is the wrong word for declining to link your own account, + * and only one person in the company can answer at all. So the card composes + * one title, one body, and exactly the affordances the reader legitimately has: + * + * - the addressed person gets the single primary **Connect ** target + * plus a plain **Not now**; + * - anybody else gets **Waiting for ** and no action at all — a + * policy-forbidden action is omitted, never rendered disabled, and the card + * must not imply a teammate can consent on their behalf; + * - once resolved it states the outcome, who resolved it, and when. + * + * The primary action is a link to the server-minted authorization URL, not an + * accept call: the OAuth callback is what resolves this interaction, so the + * card never claims success the provider has not granted. + */ +function RequestConnectionAuthorizationCard({ + interaction, + state, + isAddressee, + providerName, + addresseeLabel, + requestingAgentLabel, + resolvedByLabel, + resolvedByAgent, + onRejectInteraction, +}: { + interaction: RequestConfirmationInteraction; + state: ConnectionAuthorizationCardState; + /** Is the signed-in reader the person the server addressed? */ + isAddressee: boolean; + providerName: string; + addresseeLabel: string; + requestingAgentLabel: string | null; + resolvedByLabel: string | null; + resolvedByAgent: boolean; + onRejectInteraction?: ( + interaction: RequestConfirmationInteraction, + reason?: string, + ) => Promise | void; +}) { + const [working, setWorking] = useState(false); + const [actionError, setActionError] = useState(null); + const resolutionErrorMessage = useResolutionErrorMessage(); + const href = connectionAuthorizationHref(interaction); + const declineReason = getAdministrativeReason(interaction); + + useEffect(() => { + setActionError(null); + setWorking(false); + }, [interaction.id, interaction.status]); + + async function handleNotNow() { + if (!onRejectInteraction) return; + setWorking(true); + setActionError(null); + try { + await onRejectInteraction(interaction); + } catch (error) { + setActionError(resolutionErrorMessage(error)); + } finally { + setWorking(false); + } + } + + // One body, composed here rather than split between the header summary and a + // payload prompt that repeats the title. + // + // The server's summary is written in the second person ("needs *your* Gmail + // identity for work running as *you*") because the server addressed one + // person. Shown to a teammate it names the wrong account, so a reader who is + // not the addressee gets the same fact stated about them. Caught by rendering + // the card, not by reading it. + const agentLabel = requestingAgentLabel ?? "An agent"; + const lead = isAddressee + ? interaction.summary?.trim() + || `${agentLabel} needs your ${providerName} identity for work running as you.` + : `${agentLabel} ${state === "connected" ? "needed" : "needs"} ${addresseeLabel}'s ${providerName} identity for work running as them.`; + // Only the actionable state needs the consent boundary spelled out; the other + // states carry it in their own status line. + const consentSentence = state === "actionable" ? "No one else can complete this step." : null; + + return ( +
+

+ {consentSentence ? `${lead} ${consentSentence}` : lead} +

+ + {state === "actionable" ? ( +
+ {href ? ( +
+ + +
+ ) : ( + // A pending card with no usable target is a server-side gap, not an + // invitation to reuse an old URL. + + )} + +
+ ) : state === "waiting" ? ( + + ) : state === "connected" ? ( + + Connected by{" "} + + {/* "You" is display-cased for a badge; this is mid-sentence. */} + {(resolvedByLabel ?? addresseeLabel) === "You" ? "you" : resolvedByLabel ?? addresseeLabel} + + {resolvedByAgent ? : null} + {interaction.resolvedAt ? ` on ${formatDateTime(interaction.resolvedAt)}` : ""} + + } + /> + ) : ( + // Declined or expired. No Connect action: the authorization target this + // card carried is spent, and the agent asks again with a fresh one + // rather than the board replaying a dead URL. + + )} +
+ ); +} + +function ConnectionAuthorizationStatusLine({ + Icon, + testId, + headline, + detail, +}: { + Icon: typeof CheckCircle2; + testId: string; + headline: string; + detail: ReactNode; +}) { + return ( +
+ +
+
{headline}
+
{detail}
+
+
+ ); +} + function RequestConfirmationCard({ interaction, isPlan = false, @@ -2954,7 +3290,11 @@ function RequestCheckboxConfirmationCard({ return (
-
{interaction.payload.prompt}
+ {/* Show each piece of state once: a connection-authorization prompt is + the same sentence as the card title, so repeating it here is noise. */} + {interaction.payload.prompt === interaction.title ? null : ( +
{interaction.payload.prompt}
+ )} {interaction.payload.detailsMarkdown ? (
{interaction.payload.detailsMarkdown} @@ -3020,7 +3360,7 @@ function RequestCheckboxConfirmationCard({ / {statusText} + ) : connectionAuthorization ? ( + // One state, in the reader's own terms: "Action required", + // "Waiting for Carol", "Gmail connected". The interaction kind + // is machinery the person being asked does not need. + {statusText} ) : ( <> {isPlan ? "Plan" : interactionKindLabel(interaction.kind)} @@ -3703,6 +4067,8 @@ export function IssueThreadInteractionCard({ ? "Checkbox confirmation requested" : isSecretProposal ? "Secret binding requested" + : connectionAuthorization + ? `Connect your ${connectionAuthorization.providerName} to continue` : isToolAction ? "Tool approval requested" : interaction.kind === "request_item_verdicts" @@ -3711,7 +4077,11 @@ export function IssueThreadInteractionCard({ ? "Plan review" : "Confirmation requested")}
- {interaction.summary ? ( + {/* A connection-authorization card composes its own single body + below, because the closing sentence depends on whether the + reader is the person who may consent. Rendering the summary here + as well would be the second body PAP-17859 removed. */} + {interaction.summary && !connectionAuthorization ? (

{interaction.summary}

@@ -3759,6 +4129,20 @@ export function IssueThreadInteractionCard({ onRejectInteraction={onRejectInteraction} externalReferences={externalReferences} /> + ) : connectionAuthorization + && interaction.kind === "request_confirmation" + && connectionAuthorizationState ? ( + ) : isSecretProposal && interaction.kind === "request_confirmation" && secretProposalState ? ( {formatShortDate(interaction.resolvedAt)}
- ) : resolvedByLabel && !isToolAction ? ( + ) : resolvedByLabel && !isToolAction && !connectionAuthorization ? ( + // The connection-authorization card states its own resolver and + // timestamp inside the "Gmail connected" block, so the shared footer + // would repeat it.
; + onChange?: (next: Set) => void; + onSave?: (next: Set) => void; + loading?: boolean; + disabled?: boolean; + pending?: boolean; + getDescription?: (member: MemberMultiSelectOption) => string | null | undefined; + triggerLabel?: string; + triggerVariant?: ComponentProps["variant"]; + triggerSize?: ComponentProps["size"]; + triggerFullWidth?: boolean; + triggerClassName?: string; + contentAlign?: ComponentProps["align"]; + emptyMessage?: string; + showSelectionPreview?: boolean; + filterPlaceholder?: string; + onOpenChange?: (open: boolean) => void; +}): ReactNode { + const [open, setOpen] = useState(false); + const [filter, setFilter] = useState(""); + const [draftUserIds, setDraftUserIds] = useState>(new Set(selectedUserIds)); + const staged = Boolean(onSave); + const workingUserIds = staged ? draftUserIds : selectedUserIds; + + useEffect(() => { + if (open && staged) setDraftUserIds(new Set(selectedUserIds)); + }, [open, selectedUserIds, staged]); + + const normalizedFilter = filter.trim().toLowerCase(); + const filteredMembers = useMemo( + () => + members + .filter((member) => { + const description = getDescription?.(member) ?? member.email ?? ""; + return `${memberOptionLabel(member)} ${description}`.toLowerCase().includes(normalizedFilter); + }) + .sort((a, b) => { + const aSelected = workingUserIds.has(a.userId); + const bSelected = workingUserIds.has(b.userId); + if (aSelected !== bSelected) return aSelected ? -1 : 1; + return memberOptionLabel(a).localeCompare(memberOptionLabel(b)); + }), + [members, getDescription, normalizedFilter, workingUserIds], + ); + const selectedCount = selectedUserIds.size; + const selectedMembers = members.filter((member) => selectedUserIds.has(member.userId)); + + function setSelection(next: Set) { + if (staged) setDraftUserIds(next); + else onChange?.(next); + } + + return ( +
+ { + setOpen(nextOpen); + onOpenChange?.(nextOpen); + if (!nextOpen) setFilter(""); + }} + > + + + + +
+ setFilter(event.target.value)} + placeholder={filterPlaceholder} + className="h-8" + autoFocus + /> +
+ {loading ? ( +
+ + +
+ ) : members.length === 0 ? ( +
{emptyMessage}
+ ) : ( +
+ {filteredMembers.map((member) => { + const label = memberOptionLabel(member); + const description = getDescription?.(member) + ?? (member.email && member.email !== label ? member.email : null); + return ( + + ); + })} + {filteredMembers.length === 0 ? ( +
No matches.
+ ) : null} +
+ )} +
+ + {workingUserIds.size === 0 ? "No people selected" : `${workingUserIds.size} selected`} + +
+ {staged ? ( + + ) : null} + +
+
+
+
+ {showSelectionPreview && selectedMembers.length > 0 ? ( +
+ {selectedMembers.slice(0, 3).map((member) => ( +
+ +
+ ))} + {selectedMembers.length > 3 ? ( +

+ and {selectedMembers.length - 3} more +

+ ) : null} +
+ ) : null} +
+ ); +} diff --git a/ui/src/components/ui/radio-card.tsx b/ui/src/components/ui/radio-card.tsx index dc465bc85c..07ea6c2208 100644 --- a/ui/src/components/ui/radio-card.tsx +++ b/ui/src/components/ui/radio-card.tsx @@ -6,6 +6,12 @@ export type RadioCardOption = { value: string; title: string; description?: string; + /** + * Disable this one option while its siblings stay live. For a choice the + * viewer's capabilities forbid: the option stays legible, with its reason in + * `description`, instead of vanishing and making the scope unexplained. + */ + disabled?: boolean; }; /** @@ -72,15 +78,23 @@ export function RadioCardGroup({ if (disabled) return; const idx = options.findIndex((option) => option.value === value); if (idx === -1) return; - let nextIdx: number | null = null; + let step: number | null = null; if (event.key === "ArrowDown" || event.key === "ArrowRight") { - nextIdx = (idx + 1) % options.length; + step = 1; } else if (event.key === "ArrowUp" || event.key === "ArrowLeft") { - nextIdx = (idx - 1 + options.length) % options.length; + step = -1; } - if (nextIdx !== null) { - event.preventDefault(); - onValueChange(options[nextIdx].value); + if (step === null) return; + event.preventDefault(); + // Step over disabled options rather than landing on one: arrowing onto a + // choice the viewer cannot make would select it. + const len = options.length; + for (let hop = 1; hop <= len; hop++) { + const candidate = options[(((idx + step * hop) % len) + len) % len]; + if (!candidate.disabled) { + onValueChange(candidate.value); + return; + } } }; @@ -97,7 +111,7 @@ export function RadioCardGroup({ selected={option.value === value} title={option.title} description={option.description} - disabled={disabled} + disabled={disabled || option.disabled} tabIndex={option.value === value ? 0 : -1} onClick={() => onValueChange(option.value)} /> diff --git a/ui/src/fixtures/issueThreadInteractionFixtures.ts b/ui/src/fixtures/issueThreadInteractionFixtures.ts index deb4d2485b..e60a251099 100644 --- a/ui/src/fixtures/issueThreadInteractionFixtures.ts +++ b/ui/src/fixtures/issueThreadInteractionFixtures.ts @@ -814,6 +814,62 @@ export const pendingSecretProposalInteraction = createSecretProposalConfirmation id: "interaction-secret-proposal-pending", }); +// --------------------------------------------------------------------------- +// Connection-authorization fixtures (PAP-17835). Same interaction kind and the +// same server-addressed audience as any other confirmation; only the +// presentation payload is added, so the card never has to parse a title string +// to know what it is looking at. +// --------------------------------------------------------------------------- + +function createConnectionAuthorizationInteraction( + overrides: Partial = {}, +): RequestConfirmationInteraction { + const { payload, ...rest } = overrides; + return createRequestConfirmationInteraction({ + id: "interaction-connection-authorization-default", + title: "Connect your Gmail to continue", + summary: "Outreach Agent needs your Gmail identity for work running as you.", + createdByAgentId: "agent-codex", + addresseeUserId: issueThreadInteractionFixtureMeta.currentUserId, + resolverPolicy: "human_only", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + payload: { + version: 1, + prompt: "Connect your Gmail to continue", + acceptLabel: "Connect Gmail", + rejectLabel: "Not now", + ...payload, + connectionAuthorization: { + version: 1, + providerName: "Gmail", + connectionName: null, + requestingAgentName: "Outreach Agent", + }, + target: { + type: "custom", + key: "connection:gmail-abc:user:user-dotta", + label: "Connect Gmail", + href: "https://accounts.google.com/o/oauth2/v2/auth?client_id=paperclip", + }, + }, + ...rest, + }); +} + +export const pendingConnectionAuthorizationInteraction = createConnectionAuthorizationInteraction({ + id: "interaction-connection-authorization-pending", +}); + +export const resolvedConnectionAuthorizationInteraction = createConnectionAuthorizationInteraction({ + id: "interaction-connection-authorization-resolved", + status: "accepted", + resolvedByUserId: issueThreadInteractionFixtureMeta.currentUserId, + resolvedAt: new Date("2026-04-20T15:02:00.000Z"), + updatedAt: new Date("2026-04-20T15:02:03.000Z"), + result: { version: 1, outcome: "accepted" }, +}); + export const executedSecretProposalInteraction = createSecretProposalConfirmationInteraction({ id: "interaction-secret-proposal-executed", status: "accepted", diff --git a/ui/src/lib/interaction-audience.test.ts b/ui/src/lib/interaction-audience.test.ts index 8dc1a7bb65..8ac3a162d0 100644 --- a/ui/src/lib/interaction-audience.test.ts +++ b/ui/src/lib/interaction-audience.test.ts @@ -107,6 +107,30 @@ describe("describeInteractionAudience", () => { expect(audience.narrowedNote).toBeNull(); }); + /** + * PAP-17859, caught by rendering the card rather than reading it: + * `formatAssigneeUserLabel` returns the display-cased "You" for the signed-in + * reader, which is correct in a badge and wrong inside a sentence. + */ + it("lowercases a self-referring label inside the summary sentence", () => { + const addressed = describeInteractionAudience({ + interaction: confirmation({ addresseeUserId: "user-me" }), + addresseeLabel: "You", + }); + expect(addressed.summary).toBe("Only you can respond."); + expect(addressed.shortSummary).toBe("Only you can respond"); + + const excluded = describeInteractionAudience({ + interaction: confirmation({ + requestedResolverPolicy: "not_creator", + effectiveResolverPolicy: "not_creator", + resolverPolicyProvenance: "explicit", + }), + creatorLabel: "You", + }); + expect(excluded.summary).toBe("Anyone in the organization except you can respond."); + }); + it("falls back to a generic creator phrase when the creator label is unknown", () => { const audience = describeInteractionAudience({ interaction: confirmation({ @@ -163,6 +187,20 @@ describe("describeInteractionAudience", () => { expect(audience.label).toBe("Human only"); }); + it("names one addressed user instead of the whole board", () => { + const audience = describeInteractionAudience({ + interaction: confirmation({ + addresseeUserId: "user-alice", + requestedResolverPolicy: "human_only", + effectiveResolverPolicy: "human_only", + }), + addresseeLabel: "Alice", + }); + expect(audience.summary).toBe("Only Alice can respond."); + expect(audience.shortSummary).toBe("Only Alice can respond"); + expect(audience.label).toBe("Addressed"); + }); + it("explains a governed-action clamp", () => { const audience = describeInteractionAudience({ interaction: confirmation({ diff --git a/ui/src/lib/interaction-audience.ts b/ui/src/lib/interaction-audience.ts index 044ec13e23..398cb8a05c 100644 --- a/ui/src/lib/interaction-audience.ts +++ b/ui/src/lib/interaction-audience.ts @@ -125,6 +125,7 @@ export interface InteractionAudienceFacts { effectiveResolverPolicySource: IssueThreadInteractionEffectiveResolverPolicySource; resolverPolicyProvenance: IssueThreadInteractionResolverPolicyProvenance; hasAddressee: boolean; + isUserAddressee?: boolean; } /** @@ -143,7 +144,7 @@ export function describeInteractionAudience({ interaction: IssueThreadInteraction; /** Display label of the creating actor, when known. */ creatorLabel?: string | null; - /** Display label of the named addressee agent, when the card has one. */ + /** Display label of the named addressee, when the card has one. */ addresseeLabel?: string | null; }): InteractionAudienceDescription { return describeResolverAudience({ @@ -152,7 +153,8 @@ export function describeInteractionAudience({ requestedResolverPolicy: interaction.requestedResolverPolicy, effectiveResolverPolicySource: interaction.effectiveResolverPolicySource, resolverPolicyProvenance: interaction.resolverPolicyProvenance, - hasAddressee: Boolean(interaction.addresseeAgentId), + hasAddressee: Boolean(interaction.addresseeAgentId || interaction.addresseeUserId), + isUserAddressee: Boolean(interaction.addresseeUserId), }, creatorLabel, addresseeLabel, @@ -176,10 +178,19 @@ export function describeResolverAudience({ const policy = facts.effectiveResolverPolicy; const requestedPolicy = facts.requestedResolverPolicy; const hasAddressee = facts.hasAddressee; - const addressee = addresseeLabel?.trim() || "the addressed agent"; - const creator = creatorLabel?.trim() || "the agent that created it"; + const isUserAddressee = facts.isUserAddressee === true; + // `formatAssigneeUserLabel` returns the display-cased "You" for the signed-in + // reader, which is right for a badge and wrong mid-sentence ("Only You can + // respond."). Every use below is inside a sentence. + const midSentence = (label: string) => (label === "You" ? "you" : label); + const addressee = midSentence( + addresseeLabel?.trim() || (isUserAddressee ? "the addressed user" : "the addressed agent"), + ); + const creator = midSentence(creatorLabel?.trim() || "the agent that created it"); - const summary = policy === "human_only" + const summary = isUserAddressee + ? `Only ${addressee} can respond.` + : policy === "human_only" ? "Only a person on the board can respond — agents cannot resolve this card." : hasAddressee ? `Only ${addressee} or a person on the board can respond.` @@ -189,7 +200,9 @@ export function describeResolverAudience({ // Same fact, fewer words: a collapsed row has to answer "is this mine to // decide?" in one glance, next to the buttons that act on the answer. - const shortSummary = policy === "human_only" + const shortSummary = isUserAddressee + ? `Only ${addressee} can respond` + : policy === "human_only" ? "Only the board can respond" : hasAddressee ? `Only ${addressee} or the board can respond` @@ -228,9 +241,9 @@ export function describeResolverAudience({ policy, requestedPolicy, // A named addressee owns the response, so the label must not read "Anyone" - // while the sentence next to it names one agent. `human_only` still wins, - // because an addressed agent cannot resolve a human-only card. - label: policy !== "human_only" && hasAddressee + // while the sentence next to it names one actor. `human_only` wins for an + // agent addressee, while a user addressee is the narrower human audience. + label: (policy !== "human_only" || isUserAddressee) && hasAddressee ? "Addressed" : RESOLVER_POLICY_LABELS[policy], summary, @@ -259,7 +272,8 @@ export function describeAttentionResolverAudience( requestedResolverPolicy: audience.requestedResolverPolicy, effectiveResolverPolicySource: audience.effectiveResolverPolicySource, resolverPolicyProvenance: audience.resolverPolicyProvenance, - hasAddressee: Boolean(audience.addresseeAgentId), + hasAddressee: Boolean(audience.addresseeAgentId || audience.addresseeUserId), + isUserAddressee: Boolean(audience.addresseeUserId), }, creatorLabel: audience.createdByAgentName, addresseeLabel: audience.addresseeName, diff --git a/ui/src/lib/queryKeys.ts b/ui/src/lib/queryKeys.ts index 3c4cae75f8..d5dce3e061 100644 --- a/ui/src/lib/queryKeys.ts +++ b/ui/src/lib/queryKeys.ts @@ -29,6 +29,8 @@ export const queryKeys = { connection: (connectionId: string) => ["tools", "connection", connectionId] as const, connectionInstalls: (connectionId: string) => ["tools", "connection", connectionId, "installs"] as const, + connectionGrants: (connectionId: string) => + ["tools", "connection", connectionId, "grants"] as const, catalog: (connectionId: string) => ["tools", "connection", connectionId, "catalog"] as const, connectionActivity: (connectionId: string) => ["tools", "connection", connectionId, "activity"] as const, diff --git a/ui/src/pages/apps/AppDetail.test.tsx b/ui/src/pages/apps/AppDetail.test.tsx index 25d617f192..00677890d6 100644 --- a/ui/src/pages/apps/AppDetail.test.tsx +++ b/ui/src/pages/apps/AppDetail.test.tsx @@ -21,6 +21,12 @@ const finishAppMock = vi.hoisted(() => vi.fn()); const putConnectionInstallsMock = vi.hoisted(() => vi.fn()); const refreshCatalogMock = vi.hoisted(() => vi.fn()); const startOAuthMock = vi.hoisted(() => vi.fn()); +const listConnectionGrantsMock = vi.hoisted(() => vi.fn()); +const revokeConnectionGrantMock = vi.hoisted(() => vi.fn()); +const createConnectionGrantDelegationMock = vi.hoisted(() => vi.fn()); +const revokeConnectionGrantDelegationMock = vi.hoisted(() => vi.fn()); +const replaceConnectionGrantMembersMock = vi.hoisted(() => vi.fn()); +const startPersonalAuthorizationMock = vi.hoisted(() => vi.fn()); const listUserDirectoryMock = vi.hoisted(() => vi.fn()); const getSessionMock = vi.hoisted(() => vi.fn()); const mockNavigate = vi.hoisted(() => vi.fn()); @@ -50,6 +56,20 @@ vi.mock("@/api/tools", () => ({ archiveConnection: vi.fn(), refreshCatalog: (connectionId: string) => refreshCatalogMock(connectionId), startOAuth: (connectionId: string) => startOAuthMock(connectionId), + listConnectionGrants: (connectionId: string) => listConnectionGrantsMock(connectionId), + revokeConnectionGrant: (connectionId: string, grantId: string) => + revokeConnectionGrantMock(connectionId, grantId), + createConnectionGrantDelegation: (connectionId: string, grantId: string, agentId: string) => + createConnectionGrantDelegationMock(connectionId, grantId, agentId), + revokeConnectionGrantDelegation: ( + connectionId: string, + grantId: string, + delegationId: string, + ) => revokeConnectionGrantDelegationMock(connectionId, grantId, delegationId), + replaceConnectionGrantMembers: (connectionId: string, grantId: string, memberUserIds: string[]) => + replaceConnectionGrantMembersMock(connectionId, grantId, memberUserIds), + startPersonalAuthorization: (companyId: string, connectionId: string, input: unknown) => + startPersonalAuthorizationMock(companyId, connectionId, input), reconnectConnection: vi.fn(), }, })); @@ -155,6 +175,58 @@ function connection(overrides: Record = {}) { }; } +/** A member who may configure this connection and edit every agent. */ +function fullCapabilities(overrides: Record = {}) { + return { + canConfigure: true, + canCreateOrganizationGrant: true, + canSetCompanyInstall: true, + canConnectAsCurrentUser: true, + canManageAgentInstalls: true, + canViewOtherPersonalIdentities: false, + editableAgentIds: ["agent-1", "agent-2"], + ...overrides, + }; +} + +function organizationGrant(overrides: Record = {}) { + return { + id: "grant-org", + companyId: "company-1", + connectionId: "conn-1", + kind: "organization", + subjectUserId: null, + providerTenant: { name: "Notion workspace" }, + credentialSecretRefs: [], + status: "active", + isDefault: true, + createdByAgentId: null, + createdByUserId: "user-1", + revokedAt: null, + revokedByAgentId: null, + revokedByUserId: null, + lastUsedAt: null, + createdAt: new Date("2026-01-01T00:00:00Z"), + updatedAt: new Date("2026-01-01T00:00:00Z"), + members: [], + capabilities: { canRevoke: true, canEditAudience: true }, + ...overrides, + }; +} + +function personalGrant(overrides: Record = {}) { + return { + ...organizationGrant(), + id: "grant-user", + kind: "user", + subjectUserId: "user-1", + providerTenant: null, + isDefault: false, + capabilities: { canRevoke: true, canEditAudience: false }, + ...overrides, + }; +} + function catalogEntry(overrides: Record = {}) { return { id: "catalog-read", @@ -193,6 +265,13 @@ describe("AppDetail", () => { mockSearchParams.value = new URLSearchParams(); getConnectionMock.mockResolvedValue(connection()); getConnectionInstallsMock.mockResolvedValue({ connectionId: "conn-1", installs: [] }); + listConnectionGrantsMock.mockResolvedValue({ + connection: { id: "conn-1", uid: "conn-1" }, + grants: [], + capabilities: fullCapabilities(), + currentUserId: "user-1", + members: [], + }); listGalleryMock.mockResolvedValue({ apps: [ { @@ -265,6 +344,12 @@ describe("AppDetail", () => { authorizationUrl: "https://example.test/oauth", expiresAt: "2026-07-10T00:00:00.000Z", }); + createConnectionGrantDelegationMock.mockResolvedValue({ + id: "delegation-1", + grantId: "grant-user", + agentId: "agent-1", + }); + revokeConnectionGrantDelegationMock.mockResolvedValue({}); listUserDirectoryMock.mockResolvedValue({ users: [] }); getSessionMock.mockResolvedValue({ user: { id: "user-1", name: "Dotta", image: null }, @@ -529,10 +614,15 @@ describe("AppDetail", () => { await renderAppDetail(); - expect(container.textContent).toContain("Connect with Smoke OAuth"); + // The old generic "Connect with " block is gone: identity is now + // expressed per-identity, and a connection with no organization grant offers + // an explicit connect action instead of a single ambiguous button. + expect(container.textContent).toContain("Identities"); + expect(container.textContent).toContain("Organization identity"); + expect(container.textContent).toContain("Not connected"); expect( Array.from(container.querySelectorAll("button")).some( - (button) => button.textContent?.trim() === "Connect with Smoke OAuth", + (button) => button.textContent?.trim() === "Connect organization identity", ), ).toBe(true); }); @@ -575,16 +665,31 @@ describe("AppDetail", () => { }, }], }); + listConnectionGrantsMock.mockResolvedValue({ + connection: { id: "conn-1", uid: "conn-1" }, + grants: [organizationGrant()], + capabilities: fullCapabilities(), + currentUserId: "user-1", + members: [], + }); await renderAppDetail(); expect(container.textContent).toContain("Dotta’s Notion"); expect(container.textContent).toContain("Connected by"); expect(container.querySelector('[title="Dotta"] [data-slot="avatar"]')).toBeTruthy(); - expect(container.textContent).toContain( - "Your workspace authorization is active. Reconnect any time to replace it.", - ); - expect(container.textContent).not.toContain("Sign in again any time"); + // Identity is legible per identity, and the shared one names its audience. + // "workspace authorization" is deliberately gone: it could only ever + // describe one shared identity (PAP-17835). + expect(container.textContent).toContain("Organization identity"); + expect(container.textContent).toContain("Notion workspace"); + expect(container.textContent).toContain("All organization members"); + expect(container.textContent).not.toContain("workspace authorization"); + expect( + Array.from(container.querySelectorAll("button")).some( + (button) => button.textContent?.trim() === "Reconnect", + ), + ).toBe(true); }); it("lets Google Sheets connections add spreadsheet links from setup", async () => { @@ -706,10 +811,10 @@ describe("AppDetail", () => { await renderAppDetail(); - expect(container.textContent).toContain("Installed on agents"); + expect(container.textContent).toContain("Available to agents"); await act(async () => { Array.from(container.querySelectorAll("button")) - .find((button) => button.textContent?.includes("Choose agents to install on")) + .find((button) => button.textContent?.includes("Choose agents")) ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flushReact(); @@ -726,7 +831,13 @@ describe("AppDetail", () => { ]); }); - it("removes an existing agent grant directly from Permissions", async () => { + /** + * PAP-17859: agent availability is stated once. The tab used to stack a + * legacy "Who can use it" editor — its own Change button, per-agent remove + * buttons and Save — on top of "Available to agents", so the same fact had + * two visible editors and the reader had to guess which one won. + */ + it("shows one agent-availability model on Permissions, not the legacy access editor", async () => { mockParams.tab = "permissions"; listProfilesMock.mockResolvedValue({ profiles: [{ @@ -741,18 +852,54 @@ describe("AppDetail", () => { await renderAppDetail(); - const remove = container.querySelector('button[aria-label="Remove Coder access"]'); - expect(remove).toBeTruthy(); - await act(async () => { - remove!.dispatchEvent(new MouseEvent("click", { bubbles: true })); - }); - await flushReact(); + expect(container.textContent).toContain("Available to agents"); + expect(container.textContent).not.toContain("Who can use it"); + expect(container.textContent).not.toContain("Only specific agents"); + expect(container.querySelector('button[aria-label="Remove Coder access"]')).toBeNull(); + // Exactly one radiogroup on the tab: the install model. + expect(container.querySelectorAll('[role="radiogroup"]').length).toBe(1); + expect( + Array.from(container.querySelectorAll("button")).filter( + (button) => button.textContent?.trim() === "Change", + ), + ).toHaveLength(0); + }); - expect(finishAppMock).toHaveBeenCalledWith("company-1", "conn-1", { - enabledCatalogEntryIds: ["catalog-read", "catalog-write"], - askFirstCatalogEntryIds: ["catalog-write"], - access: { agentIds: [] }, + /** + * Viewer rule D4: a policy-forbidden action is omitted, not disabled. The + * viewer still sees the whole state — which agents have it, what each action + * is allowed to do — with nothing to press. + */ + it("gives a viewer a read-only Permissions tab with no mutation affordances", async () => { + mockParams.tab = "permissions"; + listConnectionGrantsMock.mockResolvedValue({ + connection: { id: "conn-1", uid: "conn-uid-1" }, + grants: [], + currentUserId: "user-1", + members: [], + capabilities: fullCapabilities({ + canConfigure: false, + canManageAgentInstalls: false, + canSetCompanyInstall: false, + canConnectAsCurrentUser: false, + editableAgentIds: [], + }), }); + + await renderAppDetail(); + + // State is still legible. + expect(container.textContent).toContain("Available to agents"); + expect(container.textContent).toContain("Action permissions"); + expect(container.textContent).toContain("Read repo"); + + // Nothing to mutate: no radios, no permission selects, no refresh, no save. + expect(container.querySelector('[role="radiogroup"]')).toBeNull(); + expect(container.querySelectorAll("select").length).toBe(0); + const labels = Array.from(container.querySelectorAll("button")).map((b) => b.textContent?.trim()); + for (const forbidden of ["Change", "Save", "Refresh actions", "Choose agents"]) { + expect(labels).not.toContain(forbidden); + } }); it("renders activity attribution with issue context and human resolver names", async () => { @@ -925,7 +1072,7 @@ describe("AppDetail", () => { expect(container.textContent).toContain("Needs attention"); expect(container.textContent).toContain("This app needs reconnecting"); expect(container.textContent).toContain("Token expired."); - expect(container.textContent).toContain("Who can use it"); + expect(container.textContent).toContain("Available to agents"); }); it("shows terminal OAuth failures as reconnect-required sign-in", async () => { @@ -993,4 +1140,288 @@ describe("AppDetail", () => { expect(body.length).toBeGreaterThan(0); expect(body).not.toContain(authorizationUrl); }); + + // ------------------------------------------------------------------------- + // Personal connection identity (PAP-17835). These cover the permission + // matrix in the accepted design: a member self-serving, manager oversight, + // a read-only viewer, and the audience editor's two scopes. + // ------------------------------------------------------------------------- + + function perUserConnection(overrides: Record = {}) { + return connection({ credentialPolicy: "per_user", authKind: "oauth", ...overrides }); + } + + function findButton(label: string) { + return Array.from(container.querySelectorAll("button")) + .find((button) => button.textContent?.trim() === label); + } + + it("answers 'who does this act as' in the header on every tab", async () => { + mockParams.tab = "permissions"; + getConnectionMock.mockResolvedValue(perUserConnection()); + + await renderAppDetail(); + + expect(container.textContent).toContain("Acts as each person"); + expect(container.textContent).toContain("Each person connects their own account."); + }); + + it("lets a regular member connect their own identity and never someone else's", async () => { + mockParams.tab = "setup"; + getConnectionMock.mockResolvedValue(perUserConnection()); + startPersonalAuthorizationMock.mockResolvedValue({ url: "https://accounts.example.test/authorize" }); + + await renderAppDetail(); + + // Missing personal identity is explicit, never a silent fallback. + expect(container.textContent).toContain("Your identity"); + expect(container.textContent).toContain("You have not connected your account."); + + await act(async () => { + findButton("Connect as me")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + // The subject is the signed-in user, so there is no path here to start + // consent on a coworker's behalf. + expect(startPersonalAuthorizationMock).toHaveBeenCalledWith("company-1", "conn-1", { + subjectUserId: "user-1", + returnTo: "/apps/conn-1/setup", + }); + expect(navigateTopLevelMock).toHaveBeenCalledWith("https://accounts.example.test/authorize"); + }); + + it("lets the personal identity owner grant a named agent autonomous access", async () => { + mockParams.tab = "setup"; + getConnectionMock.mockResolvedValue(perUserConnection()); + listConnectionGrantsMock.mockResolvedValue({ + connection: { id: "conn-1", uid: "conn-1" }, + grants: [personalGrant({ delegations: [] })], + capabilities: fullCapabilities(), + currentUserId: "user-1", + members: [{ userId: "user-1", name: "Dotta", email: "dotta@example.com" }], + }); + + await renderAppDetail(); + + expect(findButton("Allow autonomous access")).toBeTruthy(); + await act(async () => { + findButton("Allow autonomous access")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + const agentCheckbox = document.querySelector('button[role="checkbox"][aria-label="Allow Coder"]'); + expect(agentCheckbox).toBeTruthy(); + await act(async () => { + agentCheckbox?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await act(async () => { + Array.from(document.querySelectorAll("button")) + .find((button) => button.textContent?.trim() === "Save") + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + expect(createConnectionGrantDelegationMock).toHaveBeenCalledWith( + "conn-1", + "grant-user", + "agent-1", + ); + }); + + it("keeps a viewer read-only across identities and installs", async () => { + mockParams.tab = "setup"; + getConnectionMock.mockResolvedValue(perUserConnection()); + listConnectionGrantsMock.mockResolvedValue({ + connection: { id: "conn-1", uid: "conn-1" }, + grants: [organizationGrant({ capabilities: { canRevoke: false, canEditAudience: false } })], + capabilities: { + canConfigure: false, + canCreateOrganizationGrant: false, + canSetCompanyInstall: false, + canConnectAsCurrentUser: false, + canManageAgentInstalls: false, + canViewOtherPersonalIdentities: false, + editableAgentIds: [], + }, + currentUserId: "viewer-1", + members: [], + }); + + await renderAppDetail(); + + // State stays legible... + expect(container.textContent).toContain("Organization identity"); + expect(container.textContent).toContain("Connected"); + expect(container.textContent).toContain("All organization members"); + // ...and every mutation control is absent rather than disabled. + expect(findButton("Connect as me")).toBeUndefined(); + expect(findButton("Manage audience")).toBeUndefined(); + expect(findButton("Revoke")).toBeUndefined(); + expect(findButton("Connect organization identity")).toBeUndefined(); + }); + + it("gives a manager oversight of other people's identities", async () => { + mockParams.tab = "setup"; + getConnectionMock.mockResolvedValue(perUserConnection()); + revokeConnectionGrantMock.mockResolvedValue({ id: "grant-other", kind: "user" }); + listConnectionGrantsMock.mockResolvedValue({ + connection: { id: "conn-1", uid: "conn-1" }, + grants: [ + organizationGrant(), + personalGrant({ id: "grant-other", subjectUserId: "user-2" }), + ], + capabilities: fullCapabilities({ canViewOtherPersonalIdentities: true }), + currentUserId: "user-1", + members: [ + { userId: "user-1", name: "Dotta", email: "dotta@example.com" }, + { userId: "user-2", name: "Carol", email: "carol@example.com" }, + ], + }); + + await renderAppDetail(); + + expect(container.textContent).toContain("Other personal identities · 1"); + + await act(async () => { + Array.from(container.querySelectorAll("button")) + .find((button) => button.textContent?.includes("Other personal identities")) + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + expect(container.textContent).toContain("Carol"); + + // Scope to Carol's own row: the organization identity also offers Revoke, + // and picking the first match would silently test the wrong grant. + const carolRow = Array.from(container.querySelectorAll("div")) + .filter((row) => row.textContent?.includes("Carol") + && Array.from(row.querySelectorAll("button")).some((b) => b.textContent?.trim() === "Revoke")) + .at(-1); + expect(carolRow).toBeTruthy(); + await act(async () => { + Array.from(carolRow!.querySelectorAll("button")) + .find((button) => button.textContent?.trim() === "Revoke") + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + // Revoke is a confirmation, not a one-click action, and it never offers to + // reconnect on the other person's behalf. + const dialogText = document.body.textContent ?? ""; + expect(dialogText).toContain("Revoke this"); + expect(dialogText).toContain("They can connect again themselves"); + + await act(async () => { + Array.from(document.body.querySelectorAll("button")) + .find((button) => button.textContent?.trim() === "Revoke identity") + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + expect(revokeConnectionGrantMock).toHaveBeenCalledWith("conn-1", "grant-other"); + }); + + it("persists an empty audience as all organization members", async () => { + mockParams.tab = "setup"; + getConnectionMock.mockResolvedValue(connection({ createdByUserId: "user-1" })); + replaceConnectionGrantMembersMock.mockResolvedValue(organizationGrant({ members: [] })); + listConnectionGrantsMock.mockResolvedValue({ + connection: { id: "conn-1", uid: "conn-1" }, + grants: [organizationGrant({ + members: [{ id: "m-1", companyId: "company-1", grantId: "grant-org", subjectType: "user", subjectId: "user-2", createdAt: new Date() }], + })], + capabilities: fullCapabilities(), + currentUserId: "user-1", + members: [ + { userId: "user-1", name: "Dotta", email: "dotta@example.com" }, + { userId: "user-2", name: "Carol", email: "carol@example.com" }, + ], + }); + + await renderAppDetail(); + + expect(container.textContent).toContain("1 selected member"); + + await act(async () => { + findButton("Manage audience")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + await act(async () => { + Array.from(document.body.querySelectorAll('[role="radio"]')) + .find((option) => option.textContent?.includes("All organization members")) + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + await act(async () => { + Array.from(document.body.querySelectorAll("button")) + .find((button) => button.textContent?.trim() === "Save audience") + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + // "All members" is the empty set on the wire; the UI never says "empty". + expect(replaceConnectionGrantMembersMock).toHaveBeenCalledWith("conn-1", "grant-org", []); + }); + + it("persists a selected audience and keeps the dialog open when the server refuses", async () => { + mockParams.tab = "setup"; + getConnectionMock.mockResolvedValue(connection({ createdByUserId: "user-1" })); + replaceConnectionGrantMembersMock.mockRejectedValue( + new Error("Every audience member must be an active company member"), + ); + listConnectionGrantsMock.mockResolvedValue({ + connection: { id: "conn-1", uid: "conn-1" }, + grants: [organizationGrant({ members: [] })], + capabilities: fullCapabilities(), + currentUserId: "user-1", + members: [ + { userId: "user-1", name: "Dotta", email: "dotta@example.com" }, + { userId: "user-2", name: "Carol", email: "carol@example.com" }, + ], + }); + + await renderAppDetail(); + + await act(async () => { + findButton("Manage audience")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + await act(async () => { + Array.from(document.body.querySelectorAll('[role="radio"]')) + .find((option) => option.textContent?.includes("Selected members")) + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + await act(async () => { + Array.from(document.body.querySelectorAll("button")) + .find((button) => button.textContent?.includes("Choose people")) + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + await act(async () => { + document.body.querySelector('[aria-label="Allow Carol"]') + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + await act(async () => { + Array.from(document.body.querySelectorAll("button")) + .find((button) => button.textContent?.trim() === "Save audience") + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + expect(replaceConnectionGrantMembersMock).toHaveBeenCalledWith("conn-1", "grant-org", ["user-2"]); + // A denial keeps the dialog open with the selection intact and explains + // itself inline, rather than dropping the work into a toast. + const dialogText = document.body.textContent ?? ""; + expect(dialogText).toContain("Who can use this identity"); + expect(dialogText).toContain("Every audience member must be an active company member"); + }); }); diff --git a/ui/src/pages/apps/AppDetail.tsx b/ui/src/pages/apps/AppDetail.tsx index ddc3e5f55e..645bc814fd 100644 --- a/ui/src/pages/apps/AppDetail.tsx +++ b/ui/src/pages/apps/AppDetail.tsx @@ -37,7 +37,9 @@ import { type AppGalleryDisplayEntry, } from "./app-definition-display"; import { appTabHref, appTabLabel, isAppTabKey, type AppTabKey } from "./app-tabs"; -import { SetupPanel } from "./app-detail/SetupPanel"; +import { SetupPanel, connectionProviderName } from "./app-detail/SetupPanel"; +import { IdentitiesSection } from "./app-detail/IdentitiesSection"; +import { actsAsSummary } from "./connection-identity"; import { PermissionsPanel } from "./app-detail/PermissionsPanel"; import { TestPanel } from "./app-detail/TestPanel"; import { ReviewPanel } from "./app-detail/ReviewPanel"; @@ -104,7 +106,9 @@ export function AppDetail() { const agentsQuery = useQuery({ queryKey: queryKeys.agents.list(selectedCompanyId ?? "__none__"), queryFn: () => agentsApi.list(selectedCompanyId!), - enabled: !!selectedCompanyId && (activeTab === "permissions" || activeTab === "activity"), + enabled: !!selectedCompanyId && ( + activeTab === "setup" || activeTab === "permissions" || activeTab === "activity" + ), }); const activityQuery = useQuery({ queryKey: queryKeys.tools.connectionActivity(connectionId), @@ -122,6 +126,14 @@ export function AppDetail() { queryFn: () => authApi.getSession(), enabled: activeTab === "activity", }); + // Identity grants drive the Setup tab's Identities section and the header's + // "acts as" sentence, and Permissions reads `capabilities` from the same + // response so install controls follow one server verdict (PAP-17835). + const grantsQuery = useQuery({ + queryKey: queryKeys.tools.connectionGrants(connectionId), + queryFn: () => toolsApi.listConnectionGrants(connectionId), + enabled: !!connectionId && (activeTab === "setup" || activeTab === "permissions"), + }); const connection = connectionQuery.data; const logoEntry = useMemo( @@ -178,11 +190,11 @@ export function AppDetail() { () => askFirstCatalogIds(policiesQuery.data?.policies ?? [], connectionId), [policiesQuery.data, connectionId], ); - const access = useMemo(() => accessFrom(profile), [profile]); const install = useMemo( () => installStateFrom(installsQuery.data?.installs ?? connection?.installs), [connection?.installs, installsQuery.data?.installs], ); + const access = useMemo(() => accessFrom(profile, install), [profile, install]); const agents = agentsQuery.data ?? []; const userLabelById = useMemo(() => { const labels = buildCompanyUserLabelMap(userDirectoryQuery.data?.users); @@ -299,6 +311,127 @@ export function AppDetail() { }), }); + const invalidateGrants = () => { + queryClient.invalidateQueries({ queryKey: queryKeys.tools.connectionGrants(connectionId) }); + queryClient.invalidateQueries({ queryKey: queryKeys.tools.connection(connectionId) }); + }; + + /** + * "Connect as me" and "Reconnect" for the signed-in user's own identity. The + * subject is always the caller — the server refuses any other subject — so + * there is no path here to start consent on a coworker's behalf. + */ + const startPersonalAuth = useMutation({ + mutationFn: () => { + const subjectUserId = grantsQuery.data?.currentUserId; + if (!subjectUserId) throw new Error("Sign in again to connect your own account."); + return toolsApi.startPersonalAuthorization(selectedCompanyId!, connectionId, { + subjectUserId, + returnTo: appTabHref(connectionId, "setup"), + }); + }, + onSuccess: ({ url }) => { + const target = resolveAuthorizationTarget(url); + if (!target.ok) { + pushToast({ title: "Couldn't start sign-in", body: target.message, tone: "error" }); + return; + } + navigateTopLevel(target.url); + }, + onError: (error) => + pushToast({ + title: "Couldn't start sign-in", + body: error instanceof Error ? error.message : "Please try again.", + tone: "error", + }), + }); + + const revokeGrant = useMutation({ + mutationFn: (grantId: string) => toolsApi.revokeConnectionGrant(connectionId, grantId), + onSuccess: (grant) => { + invalidateGrants(); + pushToast({ + title: grant.kind === "user" ? "Identity revoked" : "Organization identity revoked", + body: grant.kind === "user" + ? "Agents will stop acting as this person." + : "Installed agents no longer have the shared identity.", + tone: "success", + }); + }, + onError: (error) => + pushToast({ + title: "Couldn't revoke that identity", + body: error instanceof Error ? error.message : "Please try again.", + tone: "error", + }), + }); + + const replaceDelegations = useMutation({ + mutationFn: async ({ + grantId, + currentDelegations, + agentIds, + }: { + grantId: string; + currentDelegations: Array<{ id: string; agentId: string }>; + agentIds: string[]; + }) => { + const desired = new Set(agentIds); + const existing = new Map(currentDelegations.map((delegation) => [delegation.agentId, delegation])); + await Promise.all([ + ...currentDelegations + .filter((delegation) => !desired.has(delegation.agentId)) + .map((delegation) => toolsApi.revokeConnectionGrantDelegation( + connectionId, + grantId, + delegation.id, + )), + ...agentIds + .filter((agentId) => !existing.has(agentId)) + .map((agentId) => toolsApi.createConnectionGrantDelegation(connectionId, grantId, agentId)), + ]); + }, + onSuccess: () => { + invalidateGrants(); + pushToast({ + title: "Autonomous access saved", + body: "Only the agents you selected can use your identity in autonomous runs.", + tone: "success", + }); + }, + onError: (error) => { + invalidateGrants(); + pushToast({ + title: "Couldn't save autonomous access", + body: error instanceof Error ? error.message : "Please try again.", + tone: "error", + }); + }, + }); + + // A denied or conflicting audience save keeps the dialog open with the + // selection intact, so the error is surfaced inline rather than as a toast. + const [audienceError, setAudienceError] = useState(null); + const [audienceOpenGrantId, setAudienceOpenGrantId] = useState(null); + const replaceAudience = useMutation({ + mutationFn: ({ grantId, memberUserIds }: { grantId: string; memberUserIds: string[] }) => + toolsApi.replaceConnectionGrantMembers(connectionId, grantId, memberUserIds), + onMutate: () => setAudienceError(null), + onSuccess: (grant) => { + invalidateGrants(); + setAudienceOpenGrantId(null); + pushToast({ + title: "Audience saved", + body: (grant.members?.length ?? 0) === 0 + ? "Every organization member can use this identity." + : `${grant.members?.length} ${grant.members?.length === 1 ? "member" : "members"} can use this identity.`, + tone: "success", + }); + }, + onError: (error) => + setAudienceError(error instanceof Error ? error.message : "We couldn't save that audience."), + }); + const removeApp = useMutation({ mutationFn: () => toolsApi.archiveConnection(connectionId), onSuccess: () => { @@ -414,6 +547,7 @@ export function AppDetail() { } const status = statusFor(connection); + const providerName = connectionProviderName(logoEntry, baseAppName); const needsReconnect = status.tone === "attention" && connection.healthStatus !== "unknown"; const quarantined = catalog.filter((e) => e.status === "quarantined"); const active = catalog.filter((e) => e.status !== "quarantined" && e.status !== "removed"); @@ -470,8 +604,46 @@ export function AppDetail() { onToggleApp={() => toggleEnabled.mutate()} configUpdateDisabled={updateConfig.isPending} onUpdateConfig={(config) => updateConfig.mutate(config)} - oauthStartDisabled={startOAuth.isPending} - onStartOAuth={() => startOAuth.mutate()} + identities={ + { + setAudienceError(null); + setAudienceOpenGrantId(grantId); + }} + onCloseAudience={() => { + setAudienceOpenGrantId(null); + setAudienceError(null); + }} + onConnectAsMe={() => startPersonalAuth.mutate()} + // The organization identity is a shared credential, so it goes + // through the connection-level OAuth start, not a personal one. + onConnectOrganization={() => startOAuth.mutate()} + onReconnectOrganization={() => startOAuth.mutate()} + onRevokeGrant={(grant) => revokeGrant.mutate(grant.id)} + onReplaceDelegations={(grant, agentIds) => replaceDelegations.mutate({ + grantId: grant.id, + currentDelegations: grant.delegations ?? [], + agentIds, + })} + onReplaceAudience={(grant, memberUserIds) => + replaceAudience.mutate({ grantId: grant.id, memberUserIds })} + /> + } /> : apply({ access: next })} onSaveInstall={(next) => persistInstall.mutate(next)} onRefreshActions={() => refreshTools.mutate()} onSetActionPermission={(id, next) => apply(actionPermissionMutation(id, next, enabledIds, askFirstIds))} @@ -588,6 +758,7 @@ function AppDetailHeader({ onRenameSubmit: (value: string) => void; }) { const unverifiedHost = unverifiedRemoteHost(connection); + const actsAs = actsAsSummary(connection.credentialPolicy); return (
@@ -633,6 +804,14 @@ function AppDetailHeader({ {connectionDisplaySecondaryHint(connection) && (

{connectionDisplaySecondaryHint(connection)}

)} + {/* One sentence, not a cluster of badges: whether an agent acts as you + or as the organization is the first thing every tab has to answer + (PAP-17835). It lives in the header so it carries across tabs. */} +

+ {actsAs.title} + {" · "} + {actsAs.detail} +

{owner && (
Connected by @@ -745,14 +924,35 @@ function askFirstCatalogIds(policies: ToolPolicy[], connectionId: string): Set b.targetType === "company")) { return { mode: "all", agentIds: new Set() }; } const agentIds = new Set(bindings.filter((b) => b.targetType === "agent").map((b) => b.targetId)); - if (agentIds.size === 0) return { mode: "all", agentIds: new Set() }; - return { mode: "specific", agentIds }; + if (agentIds.size > 0) return { mode: "specific", agentIds }; + return install.onAll + ? { mode: "all", agentIds: new Set() } + : { mode: "specific", agentIds: new Set(install.agentIds) }; } function galleryEntryFor( diff --git a/ui/src/pages/apps/AppsConnect.test.tsx b/ui/src/pages/apps/AppsConnect.test.tsx index 742665e95b..3c7e831817 100644 --- a/ui/src/pages/apps/AppsConnect.test.tsx +++ b/ui/src/pages/apps/AppsConnect.test.tsx @@ -118,6 +118,29 @@ function buttonContaining(text: string): HTMLButtonElement | undefined { ) as HTMLButtonElement | undefined; } +/** + * Advance past the Access step (PAP-17835), which now sits between picking a + * curated app and entering its credential. Picks "Any agent" so Continue is + * enabled without depending on the agent list. + */ +async function passAccessStep() { + const anyAgent = Array.from(document.body.querySelectorAll('[role="radio"]')) + .find((option) => option.textContent?.includes("Any agent")); + if (!anyAgent) return; + await act(async () => { + anyAgent.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + const submit = Array.from(document.body.querySelectorAll("button")).find( + (b) => b.textContent?.trim() === "Save and continue" + || b.textContent?.trim().startsWith("Continue to"), + ); + await act(async () => { + submit?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); +} + async function gotoLinkFrame(container: HTMLDivElement, url: string) { const linkInput = Array.from( container.querySelectorAll("input"), @@ -143,6 +166,10 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { apps: [ ZAPIER, ], + capabilities: { + canSetCompanyInstall: true, + companyInstallReason: null, + }, }); listApplicationsMock.mockResolvedValue({ applications: [] }); listConnectionsMock.mockResolvedValue({ connections: [] }); @@ -225,10 +252,226 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { expect(nameInput?.value).toBe("example.com/actions"); }); + // ------------------------------------------------------------------------- + // Access step (PAP-17835). Identity and agent reach are chosen before any + // credential is entered. + // ------------------------------------------------------------------------- + + it("asks both access questions before the credential and defaults per auth kind", async () => { + mockParams.appKey = "zapier"; + await render(); + + expect(container.textContent).toContain("Access"); + expect(container.textContent).toContain("Who is this credential for?"); + expect(container.textContent).toContain("Which agents can use this connection?"); + // Nothing about the credential is on screen yet. + expect(container.textContent).not.toContain("Connect Zapier"); + + const radios = Array.from(document.body.querySelectorAll('[role="radio"]')); + const justMe = radios.find((r) => r.textContent?.includes("Just me")); + const wholeOrg = radios.find((r) => r.textContent?.includes("The whole organization")); + const agentsIPick = radios.find((r) => r.textContent?.includes("Agents I pick")); + expect(justMe).toBeTruthy(); + expect(wholeOrg).toBeTruthy(); + // A key-based app is a shared service credential by default... + expect(wholeOrg?.getAttribute("aria-checked")).toBe("true"); + expect(justMe?.getAttribute("aria-checked")).toBe("false"); + // ...and the safer agent default is a picked set, not every agent. + expect(agentsIPick?.getAttribute("aria-checked")).toBe("true"); + }); + + it("blocks Continue until Agents I pick has at least one agent", async () => { + mockParams.appKey = "zapier"; + await render(); + + // "Agents I pick" with nothing picked is not a usable connection, so the + // primary action stays disabled rather than failing at submit. + expect(buttonByText("Save and continue")?.disabled).toBe(true); + + await act(async () => { + Array.from(document.body.querySelectorAll('[role="radio"]')) + .find((r) => r.textContent?.includes("Any agent")) + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + expect(buttonByText("Save and continue")?.disabled).toBe(false); + }); + + it("keeps the access selections when the wizard moves backward", async () => { + mockParams.appKey = "zapier"; + await render(); + + await act(async () => { + Array.from(document.body.querySelectorAll('[role="radio"]')) + .find((r) => r.textContent?.includes("Just me")) + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + await act(async () => { + Array.from(document.body.querySelectorAll('[role="radio"]')) + .find((r) => r.textContent?.includes("Any agent")) + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + await act(async () => { + buttonByText("Save and continue")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + expect(container.textContent).toContain("Connect Zapier"); + + await act(async () => { + buttonByText("Back")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + // Moving backward must not silently reset the identity the operator chose. + const radios = Array.from(document.body.querySelectorAll('[role="radio"]')); + expect(radios.find((r) => r.textContent?.includes("Just me"))?.getAttribute("aria-checked")) + .toBe("true"); + expect(radios.find((r) => r.textContent?.includes("Any agent"))?.getAttribute("aria-checked")) + .toBe("true"); + }); + + /** + * The identity question is answered per connection method, not per auth kind. + * + * A homogeneous fixture cannot tell those two apart: if every app on screen + * is API-key-only, a blanket rule and a per-method predicate produce exactly + * the same DOM. So both live in one gallery here — Zapier, whose only method + * is an API key, and PostHog, whose methods are identity-bearing sign-in plus + * a key its own label calls *personal*. The two mounts must disagree about + * the default, which no blanket rule can do. + * + * "Just me" also has to stay live for the API-key-only app, not disabled with + * a reason: the personal key path completes and lands on the caller's own + * grant, so disabling it would describe the product wrongly. + */ + it("decides the identity default per method, and keeps a personal key submittable", async () => { + listGalleryMock.mockResolvedValue({ apps: [ZAPIER, POSTHOG] }); + + const identityChoices = () => { + const radios = Array.from( + document.body.querySelectorAll('[role="radio"]'), + ); + return { + justMe: radios.find((r) => r.textContent?.includes("Just me")), + wholeOrg: radios.find((r) => r.textContent?.includes("The whole organization")), + }; + }; + + // --- API-key-only method: shared by default, personal still offered ------ + let root = await render(); + await act(async () => { + buttonContaining("Zapier")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + const zapier = identityChoices(); + expect(zapier.wholeOrg?.getAttribute("aria-checked")).toBe("true"); + expect(zapier.justMe?.getAttribute("aria-checked")).toBe("false"); + // Present, and genuinely selectable — not the disabled-with-reason state. + expect(zapier.justMe).toBeTruthy(); + expect(zapier.justMe?.disabled).toBe(false); + expect(document.body.textContent).not.toContain( + "This connection method supports a shared organization credential only.", + ); + + await act(async () => { + zapier.justMe?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + await act(async () => { + Array.from(document.body.querySelectorAll('[role="radio"]')) + .find((r) => r.textContent?.includes("Any agent")) + ?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + await act(async () => { + buttonByText("Save and continue")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + const keyField = container.querySelector("input[type=password]"); + await act(async () => setInputValue(keyField!, "zapier-personal-token")); + await flushReact(); + await act(async () => { + buttonByText("Connect")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + // The load-bearing assertion: an API-key-only method reaches the server as + // a personal grant. A disabled "Just me" would make this unreachable. + expect(connectAppMock).toHaveBeenCalledTimes(1); + expect(connectAppMock.mock.calls[0]?.[1]).toMatchObject({ + galleryKey: "zapier", + grantKind: "user", + }); + + // --- Same gallery, identity-bearing method: personal by default ---------- + await act(async () => root.unmount()); + document.body.innerHTML = ""; + container = document.createElement("div"); + document.body.appendChild(container); + mockParams.appKey = "posthog"; + root = await render(); + + const posthog = identityChoices(); + expect(posthog.justMe?.getAttribute("aria-checked")).toBe("true"); + expect(posthog.wholeOrg?.getAttribute("aria-checked")).toBe("false"); + expect(posthog.justMe?.disabled).toBe(false); + expect(posthog.wholeOrg?.disabled).toBe(false); + }); + + /** + * Design §"Question 2": a member who may create a personal grant but cannot + * configure a company-wide install sees **Any agent** disabled with the + * reason — not hidden. Disabling it is only half the job; Continue has to + * refuse it too, or the forbidden choice is still submittable by keyboard. + * + * Driven through AppsConnect so this proves the pre-connection capability + * returned with the gallery reaches the real create flow. + */ + it("disables Any agent and blocks Continue when the member cannot install company-wide", async () => { + mockParams.appKey = "zapier"; + listGalleryMock.mockResolvedValueOnce({ + apps: [ZAPIER], + capabilities: { + canSetCompanyInstall: false, + companyInstallReason: "Your company policy limits this choice to connection managers.", + }, + }); + await render(); + + const anyAgent = Array.from( + document.body.querySelectorAll('[role="radio"]'), + ).find((r) => r.textContent?.includes("Any agent")); + + // Visible, with the reason, and not selectable. + expect(anyAgent).toBeTruthy(); + expect(anyAgent?.disabled).toBe(true); + expect(anyAgent?.textContent).toContain( + "Your company policy limits this choice to connection managers.", + ); + // "Agents I pick" is the live alternative, so the step is not a dead end. + const pick = Array.from( + document.body.querySelectorAll('[role="radio"]'), + ).find((r) => r.textContent?.includes("Agents I pick")); + expect(pick?.disabled).toBe(false); + // Continue refuses the forbidden choice even though it is the current one. + expect(buttonByText("Save and continue")?.disabled).toBe(true); + }); + it("opens the selected app directly on its setup route", async () => { mockParams.appKey = "zapier"; await render(); + // A deep-linked app lands on Access first: identity and reach are chosen + // before the credential (PAP-17835). + expect(container.textContent).toContain("Who is this credential for?"); + await passAccessStep(); + expect(container.textContent).toContain("Connect Zapier"); expect(container.textContent).not.toContain("Pick the app you want your agents to use."); }); @@ -237,6 +480,10 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { mockParams.appKey = "posthog"; listGalleryMock.mockResolvedValueOnce({ apps: [POSTHOG] }); await render(); + // Access comes first for a curated app; the method chooser shares a screen + // with the credential fields, so it sits behind it (PAP-17835). + expect(container.textContent).toContain("Who is this credential for?"); + await passAccessStep(); expect(container.textContent).toContain("How do you want to connect?"); expect(buttonByText("Sign in with PostHog")?.getAttribute("aria-pressed")).toBe("false"); @@ -290,6 +537,10 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { mode: "tools", }, applicationId: undefined, + // PostHog's Access step defaults to "Just me" because its primary method + // is identity-bearing sign-in, and a personal API key is personal too. + // The choice was on screen and accepted, so it reaches the server. + grantKind: "user", }); }); @@ -719,7 +970,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { expect(connectAppMock.mock.calls[0]?.[1].credentialValues).toBeUndefined(); }); - it("keeps Zapier visible and uses the compact agent multi-selector throughout its wizard", async () => { + it("keeps Zapier visible and finishes without a separate access or install step", async () => { mockSearch.value = "byo=1&source=zapier"; listGalleryMock.mockResolvedValueOnce({ apps: [ @@ -746,12 +997,14 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { }); await render(); - expect(container.textContent).toContain("Step 1 of 3"); + // The pasted-URL path enters its server address in this step, so an identity + // question cannot precede it; it keeps today's every-agent default rather + // than asking (PAP-17835 leaves the Access step to the curated app path). + expect(container.textContent).toContain("Step 1 of 1"); expect(container.textContent).toContain("Connect Zapier"); expect(container.textContent).toContain("Add MCP URL"); expect(container.querySelector('img[src="https://example.com/zapier.png"]')).toBeTruthy(); expect(container.textContent).not.toContain("Pick the app you want your agents to use."); - expect(container.textContent).not.toContain("More ways to connect"); const linkInput = container.querySelector( 'input[placeholder^="https://mcp.zapier.com"]', @@ -767,66 +1020,17 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { expect(connectAppMock).toHaveBeenCalledTimes(1); expect(connectAppMock.mock.calls[0]?.[1]).toMatchObject({ link: zapierUrl, name: "Zapier" }); - expect(container.textContent).toContain("Step 2 of 3"); - expect(container.querySelector('img[src="https://example.com/zapier.png"]')).toBeTruthy(); - - await act(async () => { - buttonContaining("Only specific agents")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); - }); - await flushReact(); - - expect(container.textContent).toContain("Select agents"); - expect(container.textContent).not.toContain("Ada"); - expect(container.textContent).not.toContain("Grace"); - - await act(async () => { - buttonByText("Select agents")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); - }); - await flushReact(); - - expect(document.body.textContent).toContain("Ada"); - expect(document.body.textContent).toContain("Grace"); - - const adaCheckbox = document.body.querySelector('[aria-label="Allow Ada"]'); - await act(async () => { - adaCheckbox?.dispatchEvent(new MouseEvent("click", { bubbles: true })); - }); - await flushReact(); - await act(async () => { - buttonByText("Done")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); - }); - await flushReact(); - - expect(container.textContent).toContain("1 agent selected"); - expect(container.textContent).not.toContain("Grace"); - - await act(async () => { - buttonByText("Continue to install")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); - }); - await flushReact(); - - expect(container.textContent).toContain("Step 3 of 3"); - expect(container.textContent).toContain("Install Zapier tools?"); - expect(container.textContent).toContain("Not yet"); - - await act(async () => { - buttonContaining("Specific agents")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); - }); - await flushReact(); - - expect(container.textContent).toContain("1 agent selected"); - await act(async () => { - buttonByText("Finish setup")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); - }); - await flushReact(); + // No grantKind is sent: the pasted-URL path never offered the choice, and + // sending "user" without asking would mis-scope the credential. + expect(connectAppMock.mock.calls[0]?.[1]).not.toHaveProperty("grantKind"); expect(finishAppMock).toHaveBeenCalledWith("company-1", "conn-1", { enabledCatalogEntryIds: ["action-1"], askFirstCatalogEntryIds: [], - access: { agentIds: ["agent-1"] }, + access: "all_agents", }); expect(putConnectionInstallsMock).toHaveBeenCalledWith("conn-1", [ - { targetType: "agent", targetId: "agent-1" }, + { targetType: "company", targetId: "company-1" }, ]); }); @@ -911,6 +1115,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { buttonContaining("Google Sheets")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flushReact(); + await passAccessStep(); expect(container.textContent).toContain("Share each sheet with this email"); expect(container.textContent).toContain("robot@paperclip.iam.gserviceaccount.com"); @@ -932,6 +1137,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { buttonContaining("Google Sheets")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flushReact(); + await passAccessStep(); const textarea = container.querySelector("textarea"); await act(async () => setTextareaValue(textarea!, "https://example.com/not-a-sheet")); await flushReact(); @@ -959,16 +1165,27 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { buttonContaining("Zapier")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flushReact(); + await passAccessStep(); expect(container.textContent).toContain("Connect Zapier"); expect(nameInputFrom(container)?.value).toBe("Zapier"); expect(mockNavigate).toHaveBeenCalledWith("/apps/connect?byo=1&appKey=zapier&stage=setup"); }); - it("returns from an app key step to the BYO gallery", async () => { + it("steps back from the key step to Access, and from Access to the BYO gallery", async () => { mockSearch.value = "byo=1"; mockParams.appKey = "zapier"; await render(); + await passAccessStep(); + expect(container.textContent).toContain("Connect Zapier"); + + // Back from the credential goes to Access, not all the way out: the + // selections made there have to survive (PAP-17835). + await act(async () => { + buttonByText("Back")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + expect(container.textContent).toContain("Who is this credential for?"); await act(async () => { buttonByText("Back")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); @@ -984,6 +1201,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { buttonContaining("Zapier")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flushReact(); + await passAccessStep(); const keyField = container.querySelector("input[type=password]"); await act(async () => setInputValue(keyField!, "secret-key")); @@ -1006,6 +1224,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { buttonContaining("Zapier")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flushReact(); + await passAccessStep(); await act(async () => setInputValue(nameInputFrom(container)!, "Zapier (stdio smoke)")); const keyField = container.querySelector("input[type=password]"); @@ -1033,6 +1252,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { buttonContaining("Google Sheets")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flushReact(); + await passAccessStep(); // Default is the app name. expect(nameInputFrom(container)?.value).toBe("Google Sheets"); @@ -1068,6 +1288,7 @@ describe("AppsConnect — Connect with a link (M4 frame)", () => { buttonContaining("Google Sheets")?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); await flushReact(); + await passAccessStep(); const textarea = container.querySelector("textarea"); await act(async () => setTextareaValue( @@ -1220,9 +1441,12 @@ describe("AppsConnect — guided generic MCP flow (PAP-17087)", () => { }); await flushReact(); - expect(container.textContent).toContain("Who can use mcp.example.test?"); - expect(container.textContent).toContain("Unverified server"); - expect(container.textContent).toContain("mcp.example.test"); + // The completion summary states identity and reach once each, as three + // lines rather than badges, and still never lists the actions. + expect(container.textContent).toContain("mcp.example.test is ready."); + expect(container.textContent).toContain("Organization identity"); + expect(container.textContent).toContain("Any agent"); + expect(container.textContent).toContain("3 actions on"); expect(container.textContent).not.toContain("List things"); expect(container.querySelectorAll('[role="switch"]')).toHaveLength(0); }); diff --git a/ui/src/pages/apps/AppsConnect.tsx b/ui/src/pages/apps/AppsConnect.tsx index 7c42e140ff..9c893e075c 100644 --- a/ui/src/pages/apps/AppsConnect.tsx +++ b/ui/src/pages/apps/AppsConnect.tsx @@ -17,11 +17,14 @@ import type { LucideIcon } from "lucide-react"; import type { Agent, AppDefinition, + ConnectionGrantKind, ConnectionMethodDef, ConnectToolAppResult, FieldDef, ToolApplication, ToolConnection, + ToolConnectionAuthKind, + ToolConnectionCreateCapabilities, } from "@paperclipai/shared"; import { credentialConfigPath, getAppDefinitionForUrl, getAvailableConnectionMethod, getAvailableConnectionMethods } from "@paperclipai/shared"; import { useNavigate, useParams, useSearchParams } from "@/lib/router"; @@ -29,6 +32,7 @@ import { useCompany } from "@/context/CompanyContext"; import { useBreadcrumbs } from "@/context/BreadcrumbContext"; import { useToast } from "@/context/ToastContext"; import { queryKeys } from "@/lib/queryKeys"; +import { RadioCardGroup } from "@/components/ui/radio-card"; import { ApiError } from "@/api/client"; import { toolsApi } from "@/api/tools"; import { agentsApi } from "@/api/agents"; @@ -66,13 +70,12 @@ import { } from "./generic-mcp-connect"; import { autoExtendNotice, INSTALL_ALL_WARNING, installInfoNotice, installPayload } from "@/lib/tool-installs"; -type Step = "gallery" | "key" | "who" | "install" | "success"; +type Step = "gallery" | "access" | "key" | "success"; export type OAuthConnectPhase = "entry" | "starting" | "redirecting" | "error"; const ROUTE_STAGE_BY_STEP: Partial> = { + access: "access", key: "setup", - who: "access", - install: "install", success: "complete", }; @@ -82,21 +85,36 @@ function appConnectHref(appKey: string, step: Step): string { return `/apps/connect?${params.toString()}`; } type AppAccessSelection = "all_agents" | { agentIds: string[] }; -type InstallMode = "none" | "specific" | "all"; -const STEP_LABELS = ["Pick app", "Add your key", "Choose access", "Install tools"]; +// Access comes before credentials so the reader knows what identity and reach +// the secret is about to get before they share it (PAP-17835). +const STEP_LABELS = ["Pick app", "Access", "Add your key"]; const STEP_INDEX: Record, number> = { gallery: 0, - key: 1, - who: 2, - install: 3, + access: 1, + key: 2, }; const ZAPIER_STEP_INDEX: Record, number> = { key: 0, - who: 1, - install: 2, + access: 1, }; -const ZAPIER_STEP_LABELS = ["Add MCP URL", "Choose access", "Install tools"]; +const ZAPIER_STEP_LABELS = ["Add MCP URL"]; + +/** + * Which identity a fresh connection should default to (PAP-17835). + * + * An identity-bearing OAuth app (Gmail, Notion) is almost always personal — the + * whole point is that the agent acts as you. A service credential like an API + * key is almost always shared. Defaulting per auth kind keeps the common path a + * single Continue click without ever guessing silently: the choice is on screen. + */ +function defaultGrantKindFor( + entry: AppDefinition | null, + method: ConnectionMethodDef | null, +): ConnectionGrantKind { + const auth = method?.auth ?? (entry ? getAvailableConnectionMethod(entry)?.auth : null); + return auth === "oauth" ? "user" : "organization"; +} function isGoogleSheetsEntry(entry: AppDefinition | null): boolean { return entry?.slug === "google-sheets"; @@ -201,8 +219,14 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { const [enabled, setEnabled] = useState>({}); const [access, setAccess] = useState<"all" | "specific">("all"); const [agentIds, setAgentIds] = useState>(new Set()); - const [installMode, setInstallMode] = useState("none"); const [installAgentIds, setInstallAgentIds] = useState>(new Set()); + /** + * Access-step selections (PAP-17835). These are chosen before the credential + * and committed with it, so they must survive a failed submit and a trip + * backwards through the wizard. + */ + const [grantKind, setGrantKind] = useState("organization"); + const [installChoice, setInstallChoice] = useState<"specific" | "all">("all"); const [oauthPhase, setOAuthPhase] = useState("entry"); const [oauthError, setOAuthError] = useState(null); /** Host of the page the operator is about to be sent to, shown while redirecting. */ @@ -248,10 +272,11 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { setGoogleSheetsLinks(""); setGoogleSheetsError(null); setConnectResult(null); - setInstallMode("none"); setInstallAgentIds(new Set()); - setStep("key"); - navigate(appConnectHref(picked.slug, "key")); + setInstallChoice("specific"); + setGrantKind(defaultGrantKindFor(picked, initialMethod)); + setStep("access"); + navigate(appConnectHref(picked.slug, "access")); }; const openGallery = () => { @@ -268,8 +293,9 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { setGoogleSheetsLinks(""); setGoogleSheetsError(null); setConnectResult(null); - setInstallMode("none"); setInstallAgentIds(new Set()); + setInstallChoice("all"); + setGrantKind("organization"); setStep("gallery"); navigate(byoOnly ? "/apps/byo" : "/apps/connect?byo=1"); }; @@ -361,6 +387,18 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { }); const startOAuth = oauthStartMutation.mutate; + /** + * Commit the Access step's agent reach for a connection. Shared by the + * key-path finish and the OAuth redirect, so both routes through the wizard + * apply the same selection. + */ + const applyAccessInstalls = async (connectionId: string) => { + const installState = installChoice === "all" + ? { onAll: true, agentIds: new Set() } + : { onAll: false, agentIds: installAgentIds }; + await toolsApi.putConnectionInstalls(connectionId, installPayload(selectedCompanyId!, installState)); + }; + const connectMutation = useMutation({ mutationFn: (entryOverride?: AppDefinition) => { const connectEntry = entryOverride ?? entry; @@ -378,6 +416,7 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { ? configValues : undefined, applicationId: prefill.applicationId, + ...(grantKind === "user" ? { grantKind } : {}), }); } return toolsApi.connectApp(selectedCompanyId!, { @@ -392,11 +431,17 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { oauthClientSecret: linkOAuthClientSecret, }), applicationId: prefill.applicationId, + ...(grantKind === "user" ? { grantKind } : {}), }); }, onSuccess: (result) => { if (result.auth?.kind === "oauth") { setConnectResult(result); + // The redirect takes the operator out of the wizard, so the Access + // step's agent reach is committed now rather than after the callback. + // Best-effort: a failure here must not block the sign-in they asked for, + // and Permissions still shows the real state when they land. + void applyAccessInstalls(result.connectionId); // Discovery worked but this authorization server insists on a client the // operator registers themselves. Keep the draft and ask for it in place // rather than sending them back to the start. @@ -433,9 +478,7 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { for (const a of result.actions.readOnly) defaults[a.catalogEntryId] = true; for (const a of result.actions.canMakeChanges) defaults[a.catalogEntryId] = true; setEnabled(defaults); - setInstallMode("none"); - setInstallAgentIds(new Set()); - setAppStep("who"); + finishMutation.mutate({ result, enabled: defaults }); }, onError: (error) => { const details = error instanceof ApiError && error.body && typeof error.body === "object" @@ -502,10 +545,12 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { setGoogleSheetsLinks(""); setGoogleSheetsError(null); setConnectResult(null); + setGrantKind(defaultGrantKindFor(requestedEntry, initialMethod)); + if (!directOAuth) setInstallChoice("specific"); } - setInstallMode("none"); - setInstallAgentIds(new Set()); - setStep("key"); + // A direct-OAuth deep link is an express reconnect: it redirects on arrival, + // so there is no credential entry for an Access step to precede. + setStep(directOAuth ? "key" : "access"); if (directOAuth && ( !applicationsQuery.isFetchedAfterMount || @@ -543,29 +588,50 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { startOAuth, ]); + /** + * Commit the connection: action defaults, agent reach, and installs. + * + * Takes the connect result and the enabled set as arguments rather than + * reading them from state. The Access step removed the separate who/install + * screens, so this now runs in the same tick as the `setConnectResult` / + * `setEnabled` that precede it, where that state has not been applied yet. + */ const finishMutation = useMutation({ - mutationFn: async () => { - const enabledIds = Object.entries(enabled) + mutationFn: async (input: { result: ConnectToolAppResult; enabled: Record }) => { + const { result: connected, enabled: enabledMap } = input; + const enabledIds = Object.entries(enabledMap) .filter(([, on]) => on) .map(([id]) => id); - const selection: AppAccessSelection = - access === "all" ? "all_agents" : { agentIds: Array.from(agentIds) }; - const result = await toolsApi.finishApp(selectedCompanyId!, connectResult!.connectionId, { + const askFirstRiskLevels = new Set( + Array.isArray(connected.suggestedDefaults.askFirstRiskLevels) + ? connected.suggestedDefaults.askFirstRiskLevels.filter( + (riskLevel): riskLevel is string => typeof riskLevel === "string", + ) + : [], + ); + const askFirstIds = connected.actions.canMakeChanges + .filter((action) => enabledMap[action.catalogEntryId] && askFirstRiskLevels.has(action.riskLevel)) + .map((action) => action.catalogEntryId); + // The Access step asks one question about agent reach, so profile access + // and installs are committed to the same target set instead of drifting + // apart behind two separate wizard screens. + const selection: AppAccessSelection = installChoice === "all" + ? "all_agents" + : { agentIds: Array.from(installAgentIds) }; + const finished = await toolsApi.finishApp(selectedCompanyId!, connected.connectionId, { enabledCatalogEntryIds: enabledIds, - askFirstCatalogEntryIds: [], + askFirstCatalogEntryIds: askFirstIds, access: selection, }); - const installState = installMode === "all" - ? { onAll: true, agentIds: new Set() } - : { onAll: false, agentIds: installMode === "specific" ? installAgentIds : new Set() }; - await toolsApi.putConnectionInstalls( - connectResult!.connectionId, - installPayload(selectedCompanyId!, installState), - ); - return result; + await applyAccessInstalls(connected.connectionId); + return finished; }, onSuccess: () => setAppStep("success"), onError: (error) => { + // Creation must feel transactional: a failed commit returns the operator + // to Access with their identity and agent selections intact rather than + // stranding them on a half-made connection. + setAppStep("access"); pushToast({ title: "Couldn’t finish setup", body: error instanceof Error ? error.message : "Please try again.", @@ -675,10 +741,33 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { const stepLabels = zapierSource ? ZAPIER_STEP_LABELS : entry && getAvailableConnectionMethods(entry).length > 1 - ? ["Pick app", "Choose connection", "Choose access", "Install tools"] + ? ["Pick app", "Access", "Choose connection"] : isGoogleSheetsEntry(entry) - ? ["Pick app", "Share sheet", "Choose access", "Install tools"] + ? ["Pick app", "Access", "Share sheet"] : STEP_LABELS; + // The Access step's identity question only makes sense when there *is* a + // credential, so it reads the selected method's auth kind. + const accessStepMethod = entry + ? (connectionMethodKey + ? getAvailableConnectionMethods(entry).find((m) => m.key === connectionMethodKey) ?? null + : getAvailableConnectionMethod(entry)) + : null; + const accessStepAuthKind: ToolConnectionAuthKind = entry + ? accessStepMethod?.auth ?? "none" + : linkAuthMode === "none" + ? "none" + : linkAuthMode === "oauth" + ? "oauth" + : "api_key"; + // The primary label names the next effect, so an OAuth handoff never arrives + // unannounced. + const accessMethodIsKnown = !entry + || Boolean(connectionMethodKey) + || getAvailableConnectionMethods(entry).length === 1; + const accessSubmitLabel = accessStepAuthKind === "oauth" && accessMethodIsKnown + ? `Continue to ${entry?.name ?? "sign-in"}` + : "Save and continue"; + const stepIndex = zapierSource && step !== "gallery" && step !== "success" ? ZAPIER_STEP_INDEX[step] : step === "success" @@ -728,8 +817,9 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { setCredentials({}); setGoogleSheetsLinks(""); setGoogleSheetsError(null); - setInstallMode("none"); setInstallAgentIds(new Set()); + setInstallChoice("all"); + setGrantKind("organization"); setStep("key"); }} onRunYourOwn={() => navigate(advancedTabHref("run-your-own"))} @@ -759,7 +849,10 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { setGoogleSheetsError(null); }} submitting={connectMutation.isPending} - onBack={openGallery} + // Back returns to Access, not to the gallery: the design requires the + // identity and agent selections to survive moving backward, and + // `openGallery` resets them. + onBack={() => setAppStep("access")} onConnect={() => { if (isGoogleSheetsEntry(entry)) { const parsed = parseGoogleSheetIds(googleSheetsLinks); @@ -831,32 +924,24 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { /> )} - {step === "who" && connectResult && ( - setAppStep("key")} - onContinue={() => setAppStep("install")} - /> - )} - - {step === "install" && connectResult && ( - setAppStep("who")} - onFinish={() => finishMutation.mutate()} + capabilities={galleryQuery.data?.capabilities} + submitLabel={accessSubmitLabel} + // Leaving Access abandons the app choice entirely, so this resets the + // draft and returns to the gallery the operator came from. + onBack={() => (entry || linkUrl ? openGallery() : navigate("/apps"))} + onContinue={() => (entry ? setAppStep("key") : setStep("key"))} /> )} @@ -864,10 +949,13 @@ export function AppsConnect({ byoOnly = false }: { byoOnly?: boolean } = {}) { navigate("/apps/connections")} /> )} @@ -2083,238 +2171,188 @@ function MethodConfigField({ ); } -function WhoStep({ +/** + * Access step (PAP-17835 Surface A). + * + * Two binary questions, asked together and *before* any credential is entered, + * so the reader understands the identity and the reach the secret is about to + * get. Hick's Law: two choices, not a matrix. Both use full-row radio targets. + */ +export function AccessStep({ appName, + providerName, companyId, - access, - setAccess, - agentIds, - setAgentIds, + authKind, + grantKind, + setGrantKind, + installChoice, + setInstallChoice, + installAgentIds, + setInstallAgentIds, + capabilities, + submitLabel, onBack, onContinue, }: { appName: string; + providerName: string; companyId: string; - access: "all" | "specific"; - setAccess: (a: "all" | "specific") => void; - agentIds: Set; - setAgentIds: (s: Set) => void; + authKind: ToolConnectionAuthKind; + grantKind: ConnectionGrantKind; + setGrantKind: (kind: ConnectionGrantKind) => void; + installChoice: "specific" | "all"; + setInstallChoice: (choice: "specific" | "all") => void; + installAgentIds: Set; + setInstallAgentIds: (ids: Set) => void; + capabilities?: Pick & { + companyInstallReason?: string | null; + editableAgentIds?: string[]; + } | null; + submitLabel: string; onBack: () => void; onContinue: () => void; }) { const agentsQuery = useQuery({ queryKey: queryKeys.agents.list(companyId), queryFn: () => agentsApi.list(companyId), - enabled: access === "specific", }); - const agents: Agent[] = (agentsQuery.data ?? []).filter((a) => a.status !== "terminated"); - const canFinish = access === "all" || agentIds.size > 0; + const allAgents: Agent[] = (agentsQuery.data ?? []).filter((a) => a.status !== "terminated"); + // "Agents I pick" means agents this person may actually edit. When the server + // has not told us, fall back to every live agent rather than an empty list — + // an empty picker would read as "you have no agents". + const editableAgentIds = capabilities?.editableAgentIds; + const agents = editableAgentIds + ? allAgents.filter((agent) => editableAgentIds.includes(agent.id)) + : allAgents; + // Company-wide install is the connection creator's to give. When it is not + // available the option stays visible and disabled with the reason, so the + // scope stays legible instead of quietly disappearing. + const canSetCompanyInstall = capabilities?.canSetCompanyInstall ?? true; + const needsIdentityChoice = authKind !== "none"; + const canContinue = installChoice === "all" + ? canSetCompanyInstall + : installAgentIds.size > 0; return (
-

Who can use {appName}?

-

You can change this later from the app’s page.

+

Access

+

+ Choose who this credential represents and where agents can use it. +

-
- +
- - - {access === "specific" && ( - +

+ Which agents can use this connection? +

+ setInstallChoice(next as "specific" | "all")} + options={[ + { + value: "specific", + title: "Agents I pick", + description: "Choose one or more agents you can edit.", + }, + { + value: "all", + title: "Any agent", + description: canSetCompanyInstall + ? "Make this connection available to every agent." + : capabilities?.companyInstallReason ?? + "Only someone who can configure this connection can choose this.", + disabled: !canSetCompanyInstall, + }, + ]} /> - )} + {installChoice === "specific" ? ( +
+ +
+ ) : null} +
-
- -
); } -export function InstallStep({ - appName, - companyId, - access, - accessAgentIds, - installMode, - setInstallMode, - installAgentIds, - setInstallAgentIds, - submitting, - onBack, - onFinish, -}: { - appName: string; - companyId: string; - access: "all" | "specific"; - accessAgentIds: Set; - installMode: InstallMode; - setInstallMode: (mode: InstallMode) => void; - installAgentIds: Set; - setInstallAgentIds: (ids: Set) => void; - submitting: boolean; - onBack: () => void; - onFinish: () => void; -}) { - const agentsQuery = useQuery({ - queryKey: queryKeys.agents.list(companyId), - queryFn: () => agentsApi.list(companyId), - }); - const agents: Agent[] = (agentsQuery.data ?? []).filter((a) => a.status !== "terminated"); - const installSpecific = () => { - setInstallMode("specific"); - if (installAgentIds.size === 0 && access === "specific") setInstallAgentIds(new Set(accessAgentIds)); - }; - const extendingAgentIds = access === "all" - ? [] - : installMode === "all" - ? agents.filter((agent) => !accessAgentIds.has(agent.id)).map((agent) => agent.id) - : [...installAgentIds].filter((id) => !accessAgentIds.has(id)); - const canFinish = installMode !== "specific" || installAgentIds.size > 0; - const extendingLabel = extendingAgentIds.length === 1 - ? agents.find((agent) => agent.id === extendingAgentIds[0])?.name ?? "1 agent" - : `${extendingAgentIds.length} agents`; - - return ( -
-
-

Install {appName} tools?

-

- Access is permission. Install decides whose runs actually carry these tools. -

- -
- - {installInfoNotice(appName)} - -
- -
- - - - - {installMode === "specific" ? ( -
- -
- ) : null} - - - - {extendingAgentIds.length > 0 ? ( - - {autoExtendNotice(extendingLabel)} - - ) : null} -
-
- -
- - -
-
- ); +/** + * Summary of what the Access step committed. Three lines, not badges: identity, + * reach, and the existing action summary each said once. + */ +export function accessSummaryLines(input: { + grantKind: ConnectionGrantKind; + authKind: ToolConnectionAuthKind; + installChoice: "specific" | "all"; + installCount: number; + enabledCount: number; +}): Array<{ label: string; value: string }> { + const identity = input.authKind === "none" + ? "No identity required" + : input.grantKind === "user" + ? "Your identity" + : "Organization identity"; + const availableTo = input.installChoice === "all" + ? "Any agent" + : `${input.installCount} selected ${input.installCount === 1 ? "agent" : "agents"}`; + return [ + { label: "Identity", value: identity }, + { label: "Available to", value: availableTo }, + { + label: "Actions", + value: `${input.enabledCount} ${input.enabledCount === 1 ? "action" : "actions"} on`, + }, + ]; } function Radio({ selected }: { selected: boolean }) { @@ -2333,25 +2371,15 @@ function Radio({ selected }: { selected: boolean }) { function SuccessStep({ appName, logoUrl, - enabledCount, - access, - installMode, - installCount, + summary, onDone, }: { appName: string; logoUrl?: string | null; - enabledCount: number; - access: "all" | "specific"; - installMode: InstallMode; - installCount: number; + /** Identity / Available to / Actions, as three lines rather than badges. */ + summary: Array<{ label: string; value: string }>; onDone: () => void; }) { - const installSummary = installMode === "all" - ? "Installed on all agents" - : installMode === "specific" - ? `${installCount} ${installCount === 1 ? "agent" : "agents"} installed` - : "Permitted only"; return (
@@ -2361,18 +2389,17 @@ function SuccessStep({

{appName} is ready.

-

- {installMode === "none" - ? "Agents can use it after you install it on their Tools tab." - : "Installed agents will load it on their next run."} -

-

- {enabledCount} {enabledCount === 1 ? "action" : "actions"} on ·{" "} - {access === "all" ? "All agents can use it" : "Specific agents can use it"} · {installSummary} -

+
+ {summary.map((line) => ( +
+
{line.label}
+
{line.value}
+
+ ))} +
diff --git a/ui/src/pages/apps/Browse.test.tsx b/ui/src/pages/apps/Browse.test.tsx index 4337908a69..bc5977903c 100644 --- a/ui/src/pages/apps/Browse.test.tsx +++ b/ui/src/pages/apps/Browse.test.tsx @@ -178,7 +178,7 @@ describe("Browse store door (PAP-13254 door 1)", () => { await act(async () => { byoCard?.dispatchEvent(new MouseEvent("click", { bubbles: true })); }); - expect(navigateMock).toHaveBeenCalledWith("/apps/byo"); + expect(navigateMock).toHaveBeenCalledWith("/apps/connect?byo=1"); }); it("filters the gallery by the search query", async () => { diff --git a/ui/src/pages/apps/app-detail/IdentitiesSection.tsx b/ui/src/pages/apps/app-detail/IdentitiesSection.tsx new file mode 100644 index 0000000000..9d20e456a6 --- /dev/null +++ b/ui/src/pages/apps/app-detail/IdentitiesSection.tsx @@ -0,0 +1,571 @@ +import { useEffect, useMemo, useState, type ReactNode } from "react"; +import { ChevronRight, Loader2 } from "lucide-react"; +import type { + ConnectionAudienceMember, + ConnectionGrant, + ConnectionGrantsResponse, + ToolConnectionCredentialPolicy, +} from "@paperclipai/shared"; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; +import { InlineBanner } from "@/components/InlineBanner"; +import { MemberMultiSelect } from "@/components/MemberMultiSelect"; +import { AgentMultiSelect } from "@/components/AgentMultiSelect"; +import { RadioCardGroup } from "@/components/ui/radio-card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { cn } from "@/lib/utils"; +import { brandChipBadge } from "@/lib/status-colors"; +import { + audienceSummary, + audienceUserIds, + grantAccountLabel, + grantStatusLabel, + grantStatusTone, + memberLabel, + organizationGrant, + otherPersonalGrants, + personalGrantFor, + type GrantStatusTone, +} from "../connection-identity"; + +const STATUS_CHIP: Record = { + connected: brandChipBadge.green, + attention: brandChipBadge.amber, + inactive: brandChipBadge.gray, + missing: brandChipBadge.gray, +}; + +function StatusText({ status }: { status: ConnectionGrant["status"] | null }) { + const tone = grantStatusTone(status); + return ( + + {grantStatusLabel(status)} + + ); +} + +function formatLastUsed(value: ConnectionGrant["lastUsedAt"]): string | null { + if (!value) return null; + const parsed = value instanceof Date ? value : new Date(value); + if (Number.isNaN(parsed.getTime())) return null; + return `Last used ${parsed.toLocaleDateString(undefined, { month: "short", day: "numeric" })}`; +} + +/** + * Identity rows for the connection Setup tab (PAP-17835 Surface B). + * + * Rows are separated by space and a rule, not wrapped in one card each: "space + * separates; lines contain". Every action here is rendered from a server + * capability — a policy-forbidden action is absent rather than disabled, so a + * viewer sees the same legible state with no controls at all. + */ +export function IdentitiesSection({ + appName, + providerName, + credentialPolicy, + grantsQuery, + agents, + agentsLoading, + agentsError, + loading, + error, + onConnectAsMe, + onConnectOrganization, + onReconnectOrganization, + onRevokeGrant, + onReplaceAudience, + connectPending, + revokePending, + delegationPending, + audiencePending, + audienceError, + audienceGrantId, + onOpenAudience, + onCloseAudience, + onReplaceDelegations, +}: { + appName: string; + providerName: string; + credentialPolicy: ToolConnectionCredentialPolicy; + grantsQuery: ConnectionGrantsResponse | undefined; + agents: Array<{ + id: string; + name: string; + title?: string | null; + icon?: string | null; + status: string; + }>; + agentsLoading: boolean; + agentsError: boolean; + loading: boolean; + error: boolean; + onConnectAsMe: () => void; + onConnectOrganization: () => void; + onReconnectOrganization: () => void; + onRevokeGrant: (grant: ConnectionGrant) => void; + onReplaceAudience: (grant: ConnectionGrant, memberUserIds: string[]) => void; + connectPending: boolean; + revokePending: boolean; + delegationPending: boolean; + audiencePending: boolean; + audienceError: string | null; + /** + * The audience dialog is controlled by the page, not this section: a save that + * the server rejects has to keep the dialog open with the selection intact, + * which only the mutation's outcome knows. + */ + audienceGrantId: string | null; + onOpenAudience: (grantId: string) => void; + onCloseAudience: () => void; + onReplaceDelegations: (grant: ConnectionGrant, agentIds: string[]) => void; +}) { + const [revokeTarget, setRevokeTarget] = useState(null); + const [othersExpanded, setOthersExpanded] = useState(false); + + const grants = grantsQuery?.grants ?? []; + const capabilities = grantsQuery?.capabilities; + const currentUserId = grantsQuery?.currentUserId ?? null; + const members = grantsQuery?.members ?? []; + const orgGrant = useMemo(() => organizationGrant(grants), [grants]); + const myGrant = useMemo(() => personalGrantFor(grants, currentUserId), [grants, currentUserId]); + const others = useMemo(() => otherPersonalGrants(grants, currentUserId), [grants, currentUserId]); + const myLabel = memberLabel(members, currentUserId); + const audienceGrant = audienceGrantId + ? grants.find((grant) => grant.id === audienceGrantId) ?? null + : null; + + if (loading) { + return ( +
+ + + +
+ ); + } + + if (error) { + return ( +
+ + + We couldn't load who this connection acts as. Reload the page to try again. + +
+ ); + } + + return ( +
+ + + {agentsError ? ( +
+ + We couldn't load agents for autonomous access. Reload the page to try again. + +
+ ) : null} + +
+ {/* Organization identity — always visible, including when missing, so the + shared-vs-personal distinction never has to be inferred from absence. */} + + {orgGrant?.capabilities?.canEditAudience ? ( + + ) : null} + {orgGrant && capabilities?.canConfigure ? ( + + ) : null} + {orgGrant?.capabilities?.canRevoke && orgGrant.status !== "revoked" ? ( + + ) : null} + {!orgGrant && capabilities?.canCreateOrganizationGrant ? ( + + ) : null} + + } + /> + + {/* Your identity — the signed-in user only. Audience is never shown here: + a personal identity is consent-bound to the person who granted it. */} + {capabilities?.canConnectAsCurrentUser || myGrant ? ( + + {myGrant && myGrant.status !== "revoked" ? ( + <> + + {myGrant.capabilities?.canRevoke ? ( + + ) : null} + {myGrant.status === "active" ? ( + agent.status !== "terminated")} + loading={agentsLoading} + selectedAgentIds={new Set( + (myGrant.delegations ?? []).map((delegation) => delegation.agentId), + )} + pending={delegationPending} + triggerLabel={(myGrant.delegations?.length ?? 0) === 0 + ? "Allow autonomous access" + : `${myGrant.delegations?.length ?? 0} ${myGrant.delegations?.length === 1 ? "agent" : "agents"} allowed for autonomous runs`} + triggerSize="sm" + triggerFullWidth={false} + showSelectionPreview={false} + headerContent={( +

+ Select the named agents that may use your identity in autonomous runs. +

+ )} + onSave={(agentIds) => onReplaceDelegations(myGrant, [...agentIds])} + /> + ) : null} + + ) : capabilities?.canConnectAsCurrentUser ? ( + + ) : null} + + } + /> + ) : null} +
+ + {/* Manager oversight. A regular member never sees this list — the server + omits the grants entirely, so there is nothing to hide client-side. */} + {capabilities?.canViewOtherPersonalIdentities && others.length > 0 ? ( +
+ + {othersExpanded ? ( +
+ {others.map((grant) => ( +
+
+
+ {grantAccountLabel(grant, { + subjectLabel: memberLabel(members, grant.subjectUserId), + })} +
+ {formatLastUsed(grant.lastUsedAt) ? ( +
{formatLastUsed(grant.lastUsedAt)}
+ ) : null} +
+ + {grant.capabilities?.canRevoke && grant.status !== "revoked" ? ( + + ) : null} +
+ ))} +
+ ) : null} +
+ ) : null} + + {audienceGrant ? ( + onReplaceAudience(audienceGrant, memberUserIds)} + /> + ) : null} + + {revokeTarget ? ( + setRevokeTarget(null)} + onConfirm={() => { + onRevokeGrant(revokeTarget); + setRevokeTarget(null); + }} + /> + ) : null} +
+ ); +} + +function IdentitiesHeading() { + return ( +
+

Identities

+

+ Who agents act as when they use this connection. +

+
+ ); +} + +function IdentityRow({ + id, + title, + secondary, + status, + detail, + actions, +}: { + id?: string; + title: string; + secondary: string | null; + status: ConnectionGrant["status"] | null; + detail: string | null; + actions: ReactNode; +}) { + return ( +
+
+
+ {title} + +
+ {secondary ?
{secondary}
: null} + {detail ?
{detail}
: null} +
+
{actions}
+
+ ); +} + +/** + * "Who can use this identity" (PAP-17835 Surface C). Scope is a two-option + * radio: all organization members, persisted as no audience members, or a + * selected set. The dialog stays open on a denial so the selection survives. + */ +export function AudienceDialog({ + appName, + grant, + members, + pending, + error, + onCancel, + onSave, +}: { + appName: string; + grant: ConnectionGrant; + members: ConnectionAudienceMember[]; + pending: boolean; + error: string | null; + onCancel: () => void; + onSave: (memberUserIds: string[]) => void; +}) { + const initialSelection = useMemo(() => audienceUserIds(grant), [grant]); + const [scope, setScope] = useState<"all" | "selected">(initialSelection.size === 0 ? "all" : "selected"); + const [selected, setSelected] = useState>(initialSelection); + + useEffect(() => { + setSelected(initialSelection); + setScope(initialSelection.size === 0 ? "all" : "selected"); + }, [initialSelection]); + + const canSave = scope === "all" || selected.size > 0; + + return ( + { if (!open) onCancel(); }}> + + + Who can use this identity + + {grantAccountLabel(grant)} · {appName} + + + +
+ setScope(next as "all" | "selected")} + options={[ + { + value: "all", + title: "All organization members", + description: "Anyone in this organization can have work use this identity.", + }, + { + value: "selected", + title: "Selected members", + description: "Only the people you choose.", + }, + ]} + /> + + {scope === "selected" ? ( + ({ + userId: member.userId, + name: member.name, + email: member.email, + }))} + selectedUserIds={selected} + onChange={setSelected} + triggerLabel={selected.size === 0 + ? "Choose people" + : `${selected.size} ${selected.size === 1 ? "person" : "people"} selected`} + /> + ) : null} + +

+ This controls whose work can use the identity. It does not change which agents have the + connection. +

+ + {error ? ( + + {error} + + ) : null} +
+ + + + + +
+
+ ); +} + +/** + * Revoke confirmation (PAP-17835 Surface D). Revoke breaks active and future + * runs, so it is an `AlertDialog` and the destructive action is not the initial + * focus. The row survives afterwards showing Revoked, which keeps the context + * and the reconnect path. + */ +export function RevokeGrantDialog({ + grant, + providerName, + pending, + isOwnIdentity, + credentialPolicy, + onCancel, + onConfirm, +}: { + grant: ConnectionGrant; + providerName: string; + pending: boolean; + isOwnIdentity: boolean; + credentialPolicy: ToolConnectionCredentialPolicy; + onCancel: () => void; + onConfirm: () => void; +}) { + const personal = grant.kind === "user"; + const title = personal + ? isOwnIdentity + ? `Revoke your ${providerName} identity?` + : `Revoke this ${providerName} identity?` + : "Revoke the organization identity?"; + const body = personal + ? isOwnIdentity + ? "Agents will stop acting as you. Work that needs this identity can ask you to connect again." + : "Agents will stop acting as this person. They can connect again themselves; no one else can do it for them." + : credentialPolicy === "per_user" + ? "Installed agents lose this shared identity immediately." + : "Eligible members and installed agents will lose this shared identity immediately."; + + return ( + { if (!open) onCancel(); }}> + + + {title} + {body} + + + + Cancel + + { + event.preventDefault(); + onConfirm(); + }} + > + Revoke identity + + + + + ); +} diff --git a/ui/src/pages/apps/app-detail/PermissionsPanel.tsx b/ui/src/pages/apps/app-detail/PermissionsPanel.tsx index 1307a0653f..c2d6e1cb1b 100644 --- a/ui/src/pages/apps/app-detail/PermissionsPanel.tsx +++ b/ui/src/pages/apps/app-detail/PermissionsPanel.tsx @@ -1,28 +1,34 @@ -import { useEffect, useRef, useState } from "react"; -import { Loader2, PackageCheck, RefreshCw, X } from "lucide-react"; -import type { Agent, ToolCatalogEntry } from "@paperclipai/shared"; +import { useEffect, useRef } from "react"; +import { Loader2, RefreshCw } from "lucide-react"; +import type { Agent, ToolCatalogEntry, ToolConnectionCapabilities } from "@paperclipai/shared"; import { useSearchParams } from "@/lib/router"; import { AgentIcon } from "@/components/AgentIconPicker"; import { Button } from "@/components/ui/button"; -import { Checkbox } from "@/components/ui/checkbox"; import { AgentMultiSelect } from "@/components/AgentMultiSelect"; -import { InlineBanner } from "@/components/InlineBanner"; +import { RadioCardGroup } from "@/components/ui/radio-card"; import { cn } from "@/lib/utils"; -import { brandChipBadge } from "@/lib/status-colors"; -import { - autoExtendNotice, - INSTALL_ALL_WARNING, - installInfoNotice, - type InstallState, -} from "@/lib/tool-installs"; +import { type InstallState } from "@/lib/tool-installs"; import { QuarantinedActionsReview } from "./SetupPanel"; -import type { AccessDraft, AppDetailSectionProps } from "./types"; +import type { AppDetailSectionProps } from "./types"; type ActionPermission = "off" | "allowed" | "ask"; +/** + * Permissions tab. + * + * Agent availability is expressed **once** (PAP-17859). This panel used to + * stack a legacy "Who can use it" editor on top of "Available to agents", + * asking the reader to hold two overlapping models of the same fact and to + * guess which one wins. The install model — *Agents I pick / Any agent* — is + * now the single visible source of truth. + * + * The runtime distinction survives untouched: an install still authorizes its + * target server-side (`putConnectionInstalls` extends the app profile's + * bindings), so "installed ⊆ permitted" holds without a second editor. What is + * gone is the *user-facing* duplicate, and with it the hidden path that could + * widen access from a control the reader could not see. + */ export function PermissionsPanel({ - appName, - access, agents, install, readOnly, @@ -32,25 +38,25 @@ export function PermissionsPanel({ askFirstIds, pending, installPending, - onSaveAccess, onSaveInstall, onSetActionPermission, onReviewQuarantined, onRefreshActions, refreshPending, + capabilities, }: Pick< AppDetailSectionProps, - "access" | "agents" | "readOnly" | "canChange" | "quarantined" | "enabledIds" | "askFirstIds" | "pending" + "agents" | "readOnly" | "canChange" | "quarantined" | "enabledIds" | "askFirstIds" | "pending" > & { - appName: string; install: InstallState; installPending: boolean; - onSaveAccess: (next: AccessDraft) => void; onSaveInstall: (next: InstallState) => void; onSetActionPermission: (id: string, next: ActionPermission) => void; onReviewQuarantined: (enabledIds: string[]) => void; onRefreshActions: () => void; refreshPending: boolean; + /** Server verdict on what this caller may change here (PAP-17835). */ + capabilities: ToolConnectionCapabilities | undefined; }) { // Deep-link from the Test tab's "off" panel: ?focus={catalogEntryId} scrolls // to and highlights that action row. @@ -58,12 +64,10 @@ export function PermissionsPanel({ const focusId = searchParams.get("focus"); return (
- - @@ -76,6 +80,7 @@ export function PermissionsPanel({ disabled={pending} refreshPending={refreshPending} focusId={focusId} + canConfigure={capabilities?.canConfigure ?? false} onSetPermission={onSetActionPermission} onReviewQuarantined={onReviewQuarantined} onRefreshActions={onRefreshActions} @@ -84,250 +89,124 @@ export function PermissionsPanel({ ); } -function AccessSection({ - access, +/** + * "Available to agents" (PAP-17835 Surface E). + * + * The old section exposed the runtime's own vocabulary — "permitted only", + * "installed", an auto-extend warning — which asked the reader to hold two + * overlapping concepts to answer one question. It is now the same two-choice + * model the create flow uses: pick agents, or any agent. The runtime + * distinction still exists in code; it just stopped being the user's problem. + * + * Every control is gated on a server capability. A viewer, or a member who may + * not configure this connection, sees the summary and the agent list with no + * controls at all rather than disabled ones. + */ +function AvailableToAgentsSection({ agents, - disabled, - onSave, -}: { - access: AccessDraft; - agents: Agent[]; - disabled: boolean; - onSave: (next: AccessDraft) => void; -}) { - const [editing, setEditing] = useState(false); - const [draft, setDraft] = useState(access); - const liveAgents = agents.filter((a) => a.status !== "terminated"); - - useEffect(() => { - if (!editing) setDraft(access); - }, [access, editing]); - - const summary = - access.mode === "all" - ? "Every agent can use it" - : access.agentIds.size === 0 - ? "No agents can use it" - : `${access.agentIds.size} ${access.agentIds.size === 1 ? "agent" : "agents"} can use it`; - - const grantedAgents = liveAgents.filter((agent) => access.agentIds.has(agent.id)); - - return ( -
-
-
-

Who can use it

-

{summary}

-
- {!editing && ( - - )} -
- - {!editing && access.mode === "specific" && grantedAgents.length > 0 && ( -
- {grantedAgents.map((agent) => ( -
- - {agent.name} - -
- ))} -
- )} - - {editing && ( -
- - - - {draft.mode === "specific" && ( - setDraft({ mode: "specific", agentIds })} - disabled={disabled} - /> - )} - -
- - -
-
- )} -
- ); -} - -function InstalledSection({ - appName, - agents, - access, install, + capabilities, disabled, onSave, }: { - appName: string; agents: Agent[]; - access: AccessDraft; install: InstallState; + capabilities: ToolConnectionCapabilities | undefined; disabled: boolean; onSave: (next: InstallState) => void; }) { const liveAgents = agents.filter((a) => a.status !== "terminated"); - const hasAccess = (agentId: string) => access.mode === "all" || access.agentIds.has(agentId); - // Agents that are installed but not (yet) in the access set — installing on - // them auto-extends access server-side. Surfaced amber so it's never silent. - const extendingAgents = - access.mode === "all" - ? [] - : [...install.agentIds].filter((id) => !access.agentIds.has(id)); - const installedCount = install.onAll ? liveAgents.length : install.agentIds.size; + const canManage = capabilities?.canManageAgentInstalls ?? false; + const canSetCompanyWide = capabilities?.canSetCompanyInstall ?? false; + // "Agents I pick" is scoped to the agents this person may actually edit. The + // server decides that set; the client never infers it from a role string. + const editableAgentIds = capabilities?.editableAgentIds; + const selectableAgents = editableAgentIds + ? liveAgents.filter((agent) => editableAgentIds.includes(agent.id)) + : liveAgents; + const mode: "all" | "specific" = install.onAll ? "all" : "specific"; + const selectedAgents = liveAgents.filter((agent) => install.agentIds.has(agent.id)); + const summary = install.onAll + ? "Any agent" + : install.agentIds.size === 0 + ? "No agents yet" + : `${install.agentIds.size} ${install.agentIds.size === 1 ? "agent" : "agents"}`; return (
-
+
-

Installed on agents

-

- Whose harness carries {appName}'s tools on every run. -

+

Available to agents

+

{summary}

-
- {disabled && Saving…} + {disabled && Saving…} +
+ + {canManage ? ( +
+ { + if (next === "all") onSave({ onAll: true, agentIds: new Set() }); + else onSave({ onAll: false, agentIds: new Set(install.agentIds) }); + }} + options={[ + { + value: "specific", + title: "Agents I pick", + description: "Choose one or more agents you can edit.", + }, + { + value: "all", + title: "Any agent", + description: canSetCompanyWide + ? "Make this connection available to every agent." + : "Only someone who can configure this connection can choose this.", + }, + ].filter((option) => option.value !== "all" || canSetCompanyWide || install.onAll)} + /> + + {mode === "specific" ? ( + onSave({ onAll: false, agentIds })} + /> + ) : null} +
+ ) : ( + // Read-only: the state is still fully legible, just not editable. +
{install.onAll ? ( - - ) : install.agentIds.size > 0 ? ( - +

Every agent can use this connection.

+ ) : selectedAgents.length === 0 ? ( +

No agents have this connection yet.

) : ( - - Permitted only — not installed on any agent - +
+ {selectedAgents.map((agent) => ( +
+ + {agent.name} +
+ ))} +
)}
-
- -
- - {installInfoNotice(appName)} - - - {!install.onAll && ( - (hasAccess(agent.id) ? "has access" : "no access yet")} - renderNameSuffix={(agent) => - !hasAccess(agent.id) && install.agentIds.has(agent.id) ? ( - - will grant access - - ) : null - } - onChange={(agentIds) => onSave({ onAll: false, agentIds })} - /> - )} - - - - {extendingAgents.length > 0 ? ( - - - {autoExtendNotice( - extendingAgents.length === 1 - ? liveAgents.find((a) => a.id === extendingAgents[0])?.name ?? "1 agent" - : `${extendingAgents.length} agents`, - )}{" "} - - Review the {extendingAgents.length} access change - {extendingAgents.length === 1 ? "" : "s"} - - - - ) : null} -
+ )}
); } -function InstalledBadge({ label }: { label: string }) { - return ( - - - {label} - - ); -} - function ActionsSection({ readOnly, canChange, @@ -337,6 +216,7 @@ function ActionsSection({ disabled, refreshPending, focusId, + canConfigure, onSetPermission, onReviewQuarantined, onRefreshActions, @@ -349,6 +229,8 @@ function ActionsSection({ disabled: boolean; refreshPending: boolean; focusId?: string | null; + /** Server verdict: may this caller change this connection's configuration? */ + canConfigure: boolean; onSetPermission: (id: string, next: ActionPermission) => void; onReviewQuarantined: (enabledIds: string[]) => void; onRefreshActions: () => void; @@ -359,28 +241,35 @@ function ActionsSection({

Action permissions

- Choose what agents can do and what needs a human first. + {canConfigure + ? "Choose what agents can do and what needs a human first." + : "What agents can do, and what needs a human first."}

-
- {disabled && Saving...} - -
+ {/* Viewer rule D4: a forbidden action is omitted, not rendered disabled. + Refreshing the catalog mutates the connection, so a caller who may + not configure it never sees the control. */} + {canConfigure ? ( +
+ {disabled && Saving...} + +
+ ) : null}
- {quarantined.length > 0 && ( + {canConfigure && quarantined.length > 0 && ( ); } +const ACTION_PERMISSION_LABELS: Record = { + off: "Off", + allowed: "Allowed", + ask: "Ask a human first", +}; + function ActionGroup({ title, hint, @@ -420,6 +317,7 @@ function ActionGroup({ askFirstIds, disabled, focusId, + canConfigure, onSetPermission, }: { title: string; @@ -429,6 +327,7 @@ function ActionGroup({ askFirstIds: Set; disabled: boolean; focusId?: string | null; + canConfigure: boolean; onSetPermission: (id: string, next: ActionPermission) => void; }) { const focusRef = useRef(null); @@ -464,21 +363,28 @@ function ActionGroup({
{action.description}
)}
- + {canConfigure ? ( + + ) : ( + // Read-only: the same fact, stated rather than offered. + + {ACTION_PERMISSION_LABELS[value]} + + )} ); })} diff --git a/ui/src/pages/apps/app-detail/SetupPanel.tsx b/ui/src/pages/apps/app-detail/SetupPanel.tsx index 481d951222..58e0406bd7 100644 --- a/ui/src/pages/apps/app-detail/SetupPanel.tsx +++ b/ui/src/pages/apps/app-detail/SetupPanel.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, type ReactNode } from "react"; import type { ToolCatalogEntry, ToolConnection } from "@paperclipai/shared"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -14,8 +14,7 @@ export function SetupPanel({ appToggleDisabled, onUpdateConfig, configUpdateDisabled, - onStartOAuth, - oauthStartDisabled, + identities, }: Pick< AppDetailSectionProps, "connection" | "galleryEntry" @@ -24,18 +23,20 @@ export function SetupPanel({ appToggleDisabled: boolean; onUpdateConfig: (config: Record) => void; configUpdateDisabled: boolean; - onStartOAuth: () => void; - oauthStartDisabled: boolean; + /** + * The Identities section (PAP-17835). It replaces the old generic OAuth + * "workspace authorization" block, because that block could only ever describe + * one shared identity and this connection may act as each person instead. + */ + identities?: ReactNode; }) { const description = galleryEntry?.description ?? null; - const oauth = connection.config?.oauth; - const hasOAuthSignIn = Boolean(oauth && typeof oauth === "object" && !Array.isArray(oauth)); - const isSmokeLabFixture = connection.config?.smokeLabFixture === "oauth-http"; return (
{description && (

{description}

)} + {identities} {appDefinitionSlug(galleryEntry) === "google-sheets" && ( )} - {hasOAuthSignIn && ( - ).connectedAt)} - providerName={appDefinitionSlug(galleryEntry) === "notion" - ? "Notion" - : appDefinitionSlug(galleryEntry) === "posthog" - ? "PostHog" - : isSmokeLabFixture ? "Smoke OAuth" : "OAuth"} - disabled={oauthStartDisabled} - onStart={onStartOAuth} - /> - )}
); } +/** + * Provider label used in identity and revoke copy. Falls back to the app's own + * display name so a pasted server never reads as a generic "OAuth". + */ +export function connectionProviderName( + galleryEntry: Parameters[0], + fallback: string, +): string { + switch (appDefinitionSlug(galleryEntry)) { + case "notion": + return "Notion"; + case "posthog": + return "PostHog"; + case "gmail": + return "Gmail"; + case "google-sheets": + return "Google Sheets"; + default: + return fallback; + } +} + function PostHogConfigurationSection({ connection }: { connection: ToolConnection }) { const raw = connection.config?.methodConfig; const config = raw && typeof raw === "object" && !Array.isArray(raw) @@ -97,38 +108,6 @@ function PostHogConfigurationSection({ connection }: { connection: ToolConnectio ); } -function OAuthConnectionSection({ - connected, - providerName, - disabled, - onStart, -}: { - connected: boolean; - providerName: string; - disabled: boolean; - onStart: () => void; -}) { - return ( -
-
-
-

- {connected ? `${providerName} connected` : `Connect with ${providerName}`} -

-

- {connected - ? "Your workspace authorization is active. Reconnect any time to replace it." - : "Open the provider's consent page to finish connecting this app."} -

-
- -
-
- ); -} - function currentSpreadsheetIds(connection: ToolConnection): string[] { const raw = connection.config?.allowedSpreadsheetIds; return Array.isArray(raw) ? raw.map((value) => String(value).trim()).filter(Boolean) : []; diff --git a/ui/src/pages/apps/connection-identity.ts b/ui/src/pages/apps/connection-identity.ts new file mode 100644 index 0000000000..e907c56dd4 --- /dev/null +++ b/ui/src/pages/apps/connection-identity.ts @@ -0,0 +1,130 @@ +import type { + ConnectionAudienceMember, + ConnectionGrant, + ConnectionGrantStatus, + ToolConnectionCredentialPolicy, +} from "@paperclipai/shared"; + +/** + * Canonical user-facing vocabulary for connection identity (PAP-17835). + * + * The domain words — `grant`, `subjectUserId`, `credentialPolicy`, "empty + * audience" — never reach product copy. Everything the identity surfaces render + * resolves through this module so create, Setup, Permissions and the interaction + * card cannot drift into three different names for the same thing. + */ + +export interface ActsAsSummary { + title: string; + detail: string; +} + +export function actsAsSummary(credentialPolicy: ToolConnectionCredentialPolicy): ActsAsSummary { + switch (credentialPolicy) { + case "per_user": + return { title: "Acts as each person", detail: "Each person connects their own account." }; + case "per_user_with_fallback": + return { + title: "Uses a personal identity with organization fallback", + detail: "Agents use your account when you have one, and the organization account otherwise.", + }; + case "shared": + default: + return { title: "Acts as the organization", detail: "Agents share the organization identity." }; + } +} + +/** + * Status copy is label text, never colour alone, so it survives a monochrome or + * high-contrast rendering. "Not connected" is the explicit missing state — a + * `per_user` connection with no personal grant must never read as connected. + */ +export function grantStatusLabel(status: ConnectionGrantStatus | null): string { + switch (status) { + case "active": + return "Connected"; + case "needs_reauthorization": + return "Needs attention"; + case "expired": + return "Expired"; + case "revoked": + return "Revoked"; + default: + return "Not connected"; + } +} + +export type GrantStatusTone = "connected" | "attention" | "inactive" | "missing"; + +export function grantStatusTone(status: ConnectionGrantStatus | null): GrantStatusTone { + switch (status) { + case "active": + return "connected"; + case "needs_reauthorization": + case "expired": + return "attention"; + case "revoked": + return "inactive"; + default: + return "missing"; + } +} + +/** + * The provider's own label for the account behind a grant. Providers do not + * always return safe tenant metadata, so this falls back to a neutral phrase + * rather than exposing a secret name or ref. + */ +export function grantAccountLabel( + grant: Pick | null, + options: { subjectLabel?: string | null } = {}, +): string { + const tenantName = grant?.providerTenant?.name?.trim(); + if (tenantName) return tenantName; + if (grant?.kind === "user") return options.subjectLabel?.trim() || "Connected account"; + return "Shared credential"; +} + +/** + * Audience summary for an organization grant. Zero members is "all organization + * members" — the product never says "empty list", because that is a storage + * detail and not how anyone thinks about who may use an identity. + */ +export function audienceSummary(grant: Pick | null): string { + const count = grant?.members?.length ?? 0; + if (count === 0) return "All organization members"; + return `${count} selected ${count === 1 ? "member" : "members"}`; +} + +export function audienceUserIds(grant: Pick | null): Set { + return new Set((grant?.members ?? []) + .filter((member) => member.subjectType === "user") + .map((member) => member.subjectId)); +} + +export function organizationGrant(grants: ConnectionGrant[]): ConnectionGrant | null { + // The default organization grant is the one the resolver reaches for, so it is + // the one the Organization identity row describes. + return grants.find((grant) => grant.kind === "organization" && grant.isDefault) + ?? grants.find((grant) => grant.kind === "organization") + ?? null; +} + +export function personalGrantFor(grants: ConnectionGrant[], userId: string | null): ConnectionGrant | null { + if (!userId) return null; + return grants.find((grant) => grant.kind === "user" && grant.subjectUserId === userId) ?? null; +} + +export function otherPersonalGrants(grants: ConnectionGrant[], userId: string | null): ConnectionGrant[] { + return grants.filter((grant) => grant.kind === "user" && grant.subjectUserId !== userId); +} + +export function memberLabel( + members: ConnectionAudienceMember[], + userId: string | null, +): string | null { + if (!userId) return null; + const match = members.find((member) => member.userId === userId); + if (!match) return null; + return match.name?.trim() || match.email?.trim() || null; +} diff --git a/ui/src/pages/apps/store-cards.tsx b/ui/src/pages/apps/store-cards.tsx index a84641053c..0a500823dd 100644 --- a/ui/src/pages/apps/store-cards.tsx +++ b/ui/src/pages/apps/store-cards.tsx @@ -7,7 +7,7 @@ import { appSourceConnectHref } from "./app-connect-policy"; export const POPULAR_KEYS = ["zapier", "github", "slack", "notion", "posthog", "linear"]; /** Deep-link into the Connect wizard's bring-your-own-tool URL flow. */ -export const BYO_CONNECT_HREF = "/apps/byo"; +export const BYO_CONNECT_HREF = "/apps/connect?byo=1"; /** Zapier connects with the complete MCP URL issued by Zapier. */ export const ZAPIER_CONNECT_HREF = "/apps/connect?byo=1&source=zapier"; diff --git a/ui/src/pages/tools/McpConfigHelpDialog.test.tsx b/ui/src/pages/tools/McpConfigHelpDialog.test.tsx index 1f2f7d191e..1b6faba813 100644 --- a/ui/src/pages/tools/McpConfigHelpDialog.test.tsx +++ b/ui/src/pages/tools/McpConfigHelpDialog.test.tsx @@ -56,26 +56,29 @@ function promptTextarea(): HTMLTextAreaElement | undefined { */ describe("Paste a config — MCP config help", () => { let container: HTMLDivElement; - let root: ReturnType; + let root: ReturnType | null; beforeEach(() => { container = document.createElement("div"); document.body.appendChild(container); + root = null; copyTextToClipboardMock.mockResolvedValue(undefined); }); afterEach(() => { act(() => root?.unmount()); + root = null; container.remove(); document.body.innerHTML = ""; vi.clearAllMocks(); }); async function render() { - root = createRoot(container); + const nextRoot = createRoot(container); + root = nextRoot; const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); await act(async () => { - root.render( + nextRoot.render( @@ -83,7 +86,7 @@ describe("Paste a config — MCP config help", () => { , ); }); - return root; + return nextRoot; } async function openHelp() { @@ -143,7 +146,9 @@ describe("Paste a config — MCP config help", () => { await flushReact(); await flushReact(); - expect(document.body.textContent).toContain("select the text above and copy it"); + await vi.waitFor(() => { + expect(document.body.textContent).toContain("select the text above and copy it"); + }); }); it("makes no connection or import request when opened or copied", async () => { diff --git a/ui/src/pages/tools/PasteConfigTab.test.tsx b/ui/src/pages/tools/PasteConfigTab.test.tsx index 8afd2caf9f..ef50ba872b 100644 --- a/ui/src/pages/tools/PasteConfigTab.test.tsx +++ b/ui/src/pages/tools/PasteConfigTab.test.tsx @@ -90,6 +90,7 @@ function connectResult(overrides: Partial = {}): ConnectTo ownership: "customer", transport: "mcp_remote", authKind: "none", + credentialPolicy: "shared", status: "draft", enabled: false, config: { url: "http://127.0.0.1:8848/mcp" }, diff --git a/ui/storybook/stories/access-profiles.stories.tsx b/ui/storybook/stories/access-profiles.stories.tsx index f9310d96ee..f22616e115 100644 --- a/ui/storybook/stories/access-profiles.stories.tsx +++ b/ui/storybook/stories/access-profiles.stories.tsx @@ -275,6 +275,7 @@ const CONNECTIONS: ToolConnection[] = [ ownership: "customer", transport: "mcp_remote", authKind: "oauth", + credentialPolicy: "per_user", status: "active", transportConfig: {}, credentialSecretRefs: [], @@ -309,6 +310,7 @@ const CONNECTIONS: ToolConnection[] = [ ownership: "customer", transport: "mcp_remote", authKind: "oauth", + credentialPolicy: "per_user", status: "active", transportConfig: {}, credentialSecretRefs: [], diff --git a/ui/storybook/stories/notion-connect-flow.stories.tsx b/ui/storybook/stories/notion-connect-flow.stories.tsx index b5f4e58455..a6d8118762 100644 --- a/ui/storybook/stories/notion-connect-flow.stories.tsx +++ b/ui/storybook/stories/notion-connect-flow.stories.tsx @@ -67,6 +67,7 @@ function notionConnection(overrides: Partial = {}): ToolConnecti ownership: "dcr", transport: "mcp_remote", authKind: "oauth", + credentialPolicy: "per_user", status: "active", transportConfig: { url: "https://mcp.notion.com/mcp" }, config: { @@ -106,8 +107,6 @@ function ConnectedHost() { appToggleDisabled={false} onUpdateConfig={() => undefined} configUpdateDisabled={false} - onStartOAuth={() => undefined} - oauthStartDisabled={false} /> ); diff --git a/ui/storybook/stories/permitted-vs-installed.stories.tsx b/ui/storybook/stories/permitted-vs-installed.stories.tsx index 380b943fd8..9a1cd1fb35 100644 --- a/ui/storybook/stories/permitted-vs-installed.stories.tsx +++ b/ui/storybook/stories/permitted-vs-installed.stories.tsx @@ -5,14 +5,44 @@ import type { Agent, ToolCatalogEntry, ToolConnection, + ToolConnectionCapabilities, } from "@paperclipai/shared"; +import { IssueThreadInteractionCard } from "@/components/IssueThreadInteractionCard"; +import { + issueThreadInteractionFixtureMeta, + pendingConnectionAuthorizationInteraction, + resolvedConnectionAuthorizationInteraction, +} from "@/fixtures/issueThreadInteractionFixtures"; +import type { RequestConfirmationInteraction } from "@/lib/issue-thread-interactions"; import { queryKeys } from "@/lib/queryKeys"; import { AgentToolsTab } from "@/pages/AgentToolsTab"; import { PermissionsPanel } from "@/pages/apps/app-detail/PermissionsPanel"; -import { InstallStep } from "@/pages/apps/AppsConnect"; -import type { AccessDraft } from "@/pages/apps/app-detail/types"; +import { AccessStep } from "@/pages/apps/AppsConnect"; import type { InstallState } from "@/lib/tool-installs"; +const AGENT_IDS = ["a-sage", "a-atlas", "a-orion"]; + +/** A member who may configure this connection and edit every agent. */ +const FULL_CAPABILITIES: ToolConnectionCapabilities = { + canConfigure: true, + canCreateOrganizationGrant: true, + canSetCompanyInstall: true, + canConnectAsCurrentUser: true, + canManageAgentInstalls: true, + canViewOtherPersonalIdentities: false, + editableAgentIds: AGENT_IDS, +}; + +const VIEWER_CAPABILITIES: ToolConnectionCapabilities = { + canConfigure: false, + canCreateOrganizationGrant: false, + canSetCompanyInstall: false, + canConnectAsCurrentUser: false, + canManageAgentInstalls: false, + canViewOtherPersonalIdentities: false, + editableAgentIds: [], +}; + // --------------------------------------------------------------------------- // Phase 3b — Permitted vs Installed UX review harness (PAP-13634). // Renders the three changed surfaces at a real viewport so visual craft can be @@ -99,13 +129,18 @@ type Story = StoryObj; // --- Surface 1: App detail Permissions tab (PermissionsPanel) -------------- -function PanelHarness({ access, install }: { access: AccessDraft; install: InstallState }) { +function PanelHarness({ + install, + capabilities = FULL_CAPABILITIES, +}: { + install: InstallState; + capabilities?: ToolConnectionCapabilities; +}) { const [state, setState] = useState(install); return (
t.isReadOnly)} @@ -116,7 +151,6 @@ function PanelHarness({ access, install }: { access: AccessDraft; install: Insta pending={false} installPending={false} refreshPending={false} - onSaveAccess={() => {}} onSaveInstall={setState} onSetActionPermission={() => {}} onReviewQuarantined={() => {}} @@ -126,36 +160,61 @@ function PanelHarness({ access, install }: { access: AccessDraft; install: Insta ); } -export const AppDetailInstalledMixed: Story = { - name: "1 · App detail — mixed install + auto-extend warning", +export const AppDetailAgentsIPick: Story = { + name: "1 · App detail — Agents I pick", render: () => ( ), }; -export const AppDetailInstalledOnAll: Story = { - name: "1 · App detail — installed on all agents", +export const AppDetailAnyAgent: Story = { + name: "1 · App detail — Any agent", render: () => ( ), }; -export const AppDetailPermittedOnly: Story = { - name: "1 · App detail — permitted only (not installed)", +export const AppDetailNoAgentsYet: Story = { + name: "1 · App detail — no agents yet", render: () => ( ), }; +/** + * Viewer read-only (PAP-17835). Controls are absent, not disabled: a + * policy-forbidden action is never rendered as something to try. + */ +export const AppDetailViewerReadOnly: Story = { + name: "1 · App detail — viewer read-only", + render: () => ( + + ), +}; + +/** + * A member who may pick agents but may not make the connection company-wide: + * "Any agent" is omitted from the choice entirely. + */ +export const AppDetailMemberWithoutCompanyInstall: Story = { + name: "1 · App detail — member without company-wide install", + render: () => ( + + ), +}; + // --- Surface 2: Agent detail Tools tab (AgentToolsTab) --------------------- function SeededAgentTools() { @@ -195,18 +254,23 @@ export const AgentToolsInstalledApps: Story = { render: () => , }; -// --- Surface 3: Connect flow Install step (InstallStep) -------------------- +// --- Surface 3: Connect flow Access step (AccessStep) --------------------- +// +// The separate "who can use it" + "install tools" pair is gone: one Access step +// asks both questions before any credential is entered (PAP-17835). -function SeededInstallStep({ - access, - accessAgentIds, - initialMode, - initialInstall, +function SeededAccessStep({ + authKind, + initialGrantKind, + initialChoice, + initialAgentIds, + capabilities = { canSetCompanyInstall: true, editableAgentIds: AGENT_IDS }, }: { - access: "all" | "specific"; - accessAgentIds: Set; - initialMode: "none" | "specific" | "all"; - initialInstall: Set; + authKind: "oauth" | "api_key" | "none"; + initialGrantKind: "user" | "organization"; + initialChoice: "specific" | "all"; + initialAgentIds: Set; + capabilities?: { canSetCompanyInstall: boolean; editableAgentIds: string[] }; }) { const client = useMemo(() => { const c = new QueryClient({ @@ -215,49 +279,128 @@ function SeededInstallStep({ c.setQueryData(queryKeys.agents.list(COMPANY), AGENTS); return c; }, []); - const [mode, setMode] = useState(initialMode); - const [ids, setIds] = useState(initialInstall); + const [grantKind, setGrantKind] = useState(initialGrantKind); + const [choice, setChoice] = useState(initialChoice); + const [ids, setIds] = useState(initialAgentIds); return (
- {}} - onFinish={() => {}} + onContinue={() => {}} />
); } -export const ConnectInstallSpecific: Story = { - name: "3 · Connect — Install step (specific + auto-extend)", +export const ConnectAccessJustMePickedAgents: Story = { + name: "3 · Connect Access — Just me + Agents I pick", render: () => ( - ), }; -export const ConnectInstallAll: Story = { - name: "3 · Connect — Install step (all agents)", +export const ConnectAccessOrganizationAnyAgent: Story = { + name: "3 · Connect Access — Whole organization + Any agent", render: () => ( - + ), +}; + +/** `authKind: none` has no identity to choose, so the question is not asked. */ +export const ConnectAccessNoIdentityRequired: Story = { + name: "3 · Connect Access — no identity required", + render: () => ( + + ), +}; + +// --- Surface 4: the "Connect your Gmail to continue" card ------------------- +// +// One `request_confirmation`, three readings (PAP-17859). The card no longer +// falls through to the generic Approve / Revise… / Reject layout: consent is +// the addressed person's alone, so the affordances change with the reader, and +// a policy-forbidden action is omitted rather than shown greyed out. + +const AUTHORIZATION_USER_LABELS = new Map([ + [issueThreadInteractionFixtureMeta.currentUserId, "Carol"], +]); + +function AuthorizationCardHarness({ + interaction, + currentUserId, +}: { + interaction: RequestConfirmationInteraction; + currentUserId: string; +}) { + return ( +
+ {}} + onRejectInteraction={async () => {}} + /> +
+ ); +} + +export const AuthorizationAddressed: Story = { + name: "4 · Connect Gmail — addressed user", + render: () => ( + + ), +}; + +export const AuthorizationOtherReader: Story = { + name: "4 · Connect Gmail — another reader waiting", + render: () => ( + + ), +}; + +export const AuthorizationResolved: Story = { + name: "4 · Connect Gmail — resolved", + render: () => ( + ), }; diff --git a/ui/storybook/stories/personal-connection-identity.stories.tsx b/ui/storybook/stories/personal-connection-identity.stories.tsx new file mode 100644 index 0000000000..063454adf1 --- /dev/null +++ b/ui/storybook/stories/personal-connection-identity.stories.tsx @@ -0,0 +1,319 @@ +import { useState } from "react"; +import type { Meta, StoryObj } from "@storybook/react-vite"; +import type { + ConnectionGrant, + ConnectionGrantsResponse, + ToolConnectionCapabilities, + ToolConnectionCredentialPolicy, +} from "@paperclipai/shared"; +import { IdentitiesSection } from "@/pages/apps/app-detail/IdentitiesSection"; +import { actsAsSummary } from "@/pages/apps/connection-identity"; + +// --------------------------------------------------------------------------- +// PAP-17835 — personal connection identity UX review harness. +// +// One story per item in the design's "Test and screenshot gate", so the whole +// gate can be rendered and inspected at 1440x900 and 390x844 without seeding a +// live company. Fixtures are self-contained. +// --------------------------------------------------------------------------- + +const CURRENT_USER = "user-carol"; + +const MEMBERS = [ + { userId: CURRENT_USER, name: "Carol Danvers", email: "carol@example.com" }, + { userId: "user-dotta", name: "Dotta", email: "dotta@example.com" }, + { userId: "user-sam", name: "Sam Rivera", email: "sam@example.com" }, + { userId: "user-priya", name: "Priya Raman", email: "priya@example.com" }, +]; + +const MEMBER_CAPABILITIES: ToolConnectionCapabilities = { + canConfigure: true, + canCreateOrganizationGrant: true, + canSetCompanyInstall: true, + canConnectAsCurrentUser: true, + canManageAgentInstalls: true, + canViewOtherPersonalIdentities: false, + editableAgentIds: ["a-outreach", "a-research"], +}; + +const MANAGER_CAPABILITIES: ToolConnectionCapabilities = { + ...MEMBER_CAPABILITIES, + canViewOtherPersonalIdentities: true, +}; + +/** A viewer sees the same legible state with no mutation controls at all. */ +const VIEWER_CAPABILITIES: ToolConnectionCapabilities = { + canConfigure: false, + canCreateOrganizationGrant: false, + canSetCompanyInstall: false, + canConnectAsCurrentUser: false, + canManageAgentInstalls: false, + canViewOtherPersonalIdentities: false, + editableAgentIds: [], +}; + +function grant(overrides: Partial = {}): ConnectionGrant { + return { + id: "grant-org", + companyId: "company-1", + connectionId: "conn-1", + kind: "organization", + subjectUserId: null, + providerTenant: null, + credentialSecretRefs: [], + status: "active", + isDefault: true, + createdByAgentId: null, + createdByUserId: CURRENT_USER, + revokedAt: null, + revokedByAgentId: null, + revokedByUserId: null, + lastUsedAt: null, + createdAt: new Date("2026-08-01T10:00:00Z"), + updatedAt: new Date("2026-08-01T10:00:00Z"), + members: [], + capabilities: { canRevoke: true, canEditAudience: true }, + ...overrides, + }; +} + +function audienceMembers(userIds: string[]) { + return userIds.map((userId, index) => ({ + id: `member-${index}`, + companyId: "company-1", + grantId: "grant-org", + subjectType: "user" as const, + subjectId: userId, + createdAt: new Date("2026-08-01T10:00:00Z"), + })); +} + +function personalGrant(overrides: Partial = {}): ConnectionGrant { + return grant({ + id: "grant-carol", + kind: "user", + subjectUserId: CURRENT_USER, + isDefault: false, + lastUsedAt: new Date("2026-08-19T09:12:00Z"), + capabilities: { canRevoke: true, canEditAudience: false }, + ...overrides, + }); +} + +/** + * Renders the identity surface the way the Setup tab does, including the header + * "acts as" sentence — that sentence is the at-a-glance answer the design + * requires, so a screenshot of the rows alone would not show the whole state. + */ +function IdentitiesHarness({ + credentialPolicy = "per_user", + grants, + capabilities = MEMBER_CAPABILITIES, + loading = false, + error = false, + audienceGrantId = null, + audienceError = null, +}: { + credentialPolicy?: ToolConnectionCredentialPolicy; + grants: ConnectionGrant[]; + capabilities?: ToolConnectionCapabilities; + loading?: boolean; + error?: boolean; + audienceGrantId?: string | null; + audienceError?: string | null; +}) { + const [openAudience, setOpenAudience] = useState(audienceGrantId); + const response: ConnectionGrantsResponse = { + connection: { id: "conn-1", uid: "conn-1" }, + grants, + capabilities, + currentUserId: CURRENT_USER, + members: MEMBERS, + }; + const actsAs = actsAsSummary(credentialPolicy); + return ( +
+
+

Gmail

+

+ {actsAs.title} + {" · "} + {actsAs.detail} +

+
+ setOpenAudience(null)} + onConnectAsMe={() => {}} + onConnectOrganization={() => {}} + onReconnectOrganization={() => {}} + onRevokeGrant={() => {}} + onReplaceDelegations={() => {}} + onReplaceAudience={() => {}} + /> +
+ ); +} + +const meta: Meta = { + title: "Reviews/PAP-17835 Personal connection identity", + parameters: { layout: "fullscreen" }, +}; +export default meta; +type Story = StoryObj; + +// --- Gate 3: personal connected, organization missing ---------------------- + +export const SetupPersonalConnectedOrganizationMissing: Story = { + name: "3 · Setup — your identity connected, organization missing", + render: () => ( + + ), +}; + +export const SetupPersonalNotConnected: Story = { + name: "3b · Setup — your identity not connected (no silent fallback)", + render: () => , +}; + +// --- Gate 4: organization identity with a selected audience, manager view --- + +export const SetupManagerOversight: Story = { + name: "4 · Setup — selected audience + manager oversight", + render: () => ( + + ), +}; + +// --- Gate 5: audience editor, both scopes ---------------------------------- + +export const AudienceEditorAllMembers: Story = { + name: "5 · Audience editor — all organization members", + render: () => ( + + ), +}; + +export const AudienceEditorSelectedMembers: Story = { + name: "5b · Audience editor — selected members", + render: () => ( + + ), +}; + +// --- Gate 6: post-revoke state -------------------------------------------- + +export const SetupAfterRevoke: Story = { + name: "6 · Setup — after revoke, row stays with a reconnect path", + render: () => ( + + ), +}; + +// --- Gate 7: viewer read-only -------------------------------------------- + +export const SetupViewerReadOnly: Story = { + name: "7 · Setup — viewer read-only", + render: () => ( + + ), +}; + +// --- Gate 9: loading, empty, and a server denial -------------------------- + +export const SetupLoading: Story = { + name: "9 · Setup — loading", + render: () => , +}; + +export const SetupLoadFailed: Story = { + name: "9b · Setup — identities could not be loaded", + render: () => , +}; + +/** + * A refused audience save keeps the dialog open with the selection intact and + * explains itself inline, rather than dropping the work into a toast. + */ +export const AudienceEditorServerDenial: Story = { + name: "9c · Audience editor — server denial keeps the selection", + render: () => ( + + ), +};