diff --git a/docs/shared-oauth.md b/docs/shared-oauth.md new file mode 100644 index 000000000..8f065e332 --- /dev/null +++ b/docs/shared-oauth.md @@ -0,0 +1,47 @@ +# Share TanStack login with Source + +TanStack.com is the authorization server for Source browser login and MCP clients. Source owns its accounts and repository permissions, and links them to the stable TanStack user ID. An email match never links an existing Source account. A verified GitHub ID shared by both products can connect a TanStack login to the existing Source GitHub account. Conflicting Source account links require a person to resolve them. + +## Enable the integration + +1. Apply `drizzle/migrations/0043_shared_oauth.sql` with the normal database migration command. It adds resource and refresh-grant fields, and widens OAuth client IDs to text. Existing OAuth records keep their null resource and their current behavior. Do not use `db:push` against production. +2. Generate a random 32-byte secret and configure the same `TANSTACK_SOURCE_INTROSPECTION_SECRET` in TanStack.com and the Source Worker. Keep it out of browser configuration and source control. Only the Source server can introspect Source grants. +3. Deploy TanStack.com before enabling the Source integration. Source also needs its D1 migration `0045_tanstack_identity.sql`. +4. Sign into the existing Source account, then connect TanStack in Settings > Connected accounts. New people choose Continue with TanStack and pick a Source username. No existing account or organization membership moves. +5. Add `https://source.tanstack.com/mcp` to an OAuth-capable MCP client. Discovery points to TanStack.com. Consent identifies the app and its Source permissions. + +## Grant boundaries + +- `https://tanstack.com/oauth/userinfo`, scope `identity`: returns the stable account ID, name, email and suggested username. The preregistered public client `tanstack-source` has exactly one redirect, `https://source.tanstack.com/auth/tanstack/callback`. PKCE and browser state bind the login. This is OAuth identity lookup, not OpenID Connect. +- `https://source.tanstack.com/mcp`, scope `source`: lets the client act as the linked Source person at `/mcp`. It grants no extra repository permissions, and never works at Source REST, git or browser-session endpoints. The consent page links to Source account setup before approval. +- Existing null-resource `api` grants continue to work. New grants for `https://tanstack.com/mcp` work on the existing TanStack API, with scope `api`. + +Codes are single-use and bound to the client, redirect, PKCE challenge and resource. Access tokens expire in one hour. Resource-bound refresh tokens rotate, expire 30 days after the original grant, and revoke the whole grant on replay. Revocation is available through `/oauth/revoke` and TanStack connected apps. Source introspects each request, so revocation takes effect on the next request. + +Existing personal API keys, legacy OAuth access tokens and refresh tokens stay valid. Source personal, agent and job tokens keep their existing policy and storage paths. + +## Validate without production data + +Run the pure tests with: + +```sh +pnpm exec vitest run --config vitest.chat.config.ts harness-tests/core/shared-oauth.test.ts +``` + +The database tests require an isolated local PostgreSQL database named `shared_oauth_test` on `127.0.0.1`. They create, truncate and drop only their fixture tables. Never point them at a real application database. + +```sh +OAUTH_TEST_DATABASE_URL=postgres://oauth_test@127.0.0.1:55439/shared_oauth_test pnpm exec vitest run --config vitest.chat.config.ts harness-tests/core/shared-oauth-db.test.ts +``` + +The implementation follows [MCP authorization](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization), RFC 9728 resource metadata, RFC 8414 discovery, RFC 8707 resource indicators and RFC 9207 issuer responses. Dynamic registration supports existing MCP clients. Client ID metadata documents are not advertised. + +## Show the current TanStack account on Source + +`GET /oauth/session-hint` uses the existing TanStack browser session and allows credentialed CORS only from `https://source.tanstack.com`. It returns a display name and avatar URL, never an access token, session cookie or account ID. Signed-out requests return no account. Responses are private and never cached. + +Source fetches the hint in the browser and shows the avatar with **Continue as [name] on TanStack**. If the hint is unavailable, Source shows **Continue with TanStack**. GitHub remains a separate sign-in choice. Both TanStack buttons begin the same PKCE OAuth flow. The hint grants no access and is not used to link accounts. + +The authenticated `/oauth/userinfo` response includes `github_id`, taken from the GitHub account attached to the authenticated TanStack user. Source uses this verified provider ID in browser sign-in and MCP authentication to find the same Source person, including when the emails differ. This field is never taken from the session hint or browser input. + +Source resolves the MCP caller from an existing TanStack identity link or an unambiguous verified GitHub identity. Authentication reads do not create links or accounts. Conflicting Source links are refused. If a TanStack account has more than one GitHub identity, no automatic GitHub match is offered. diff --git a/drizzle/migrations/0043_shared_oauth.sql b/drizzle/migrations/0043_shared_oauth.sql new file mode 100644 index 000000000..24e201a71 --- /dev/null +++ b/drizzle/migrations/0043_shared_oauth.sql @@ -0,0 +1,12 @@ +ALTER TABLE "oauth_mcp_access_tokens" ALTER COLUMN "client_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "oauth_mcp_authorization_codes" ALTER COLUMN "client_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "oauth_mcp_refresh_tokens" ALTER COLUMN "client_id" SET DATA TYPE text;--> statement-breakpoint +ALTER TABLE "oauth_mcp_access_tokens" ADD COLUMN "resource" text;--> statement-breakpoint +ALTER TABLE "oauth_mcp_access_tokens" ADD COLUMN "grant_id" uuid;--> statement-breakpoint +ALTER TABLE "oauth_mcp_authorization_codes" ADD COLUMN "resource" text;--> statement-breakpoint +ALTER TABLE "oauth_mcp_refresh_tokens" ADD COLUMN "resource" text;--> statement-breakpoint +ALTER TABLE "oauth_mcp_refresh_tokens" ADD COLUMN "scope" text DEFAULT 'api' NOT NULL;--> statement-breakpoint +ALTER TABLE "oauth_mcp_refresh_tokens" ADD COLUMN "consumed_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "oauth_mcp_refresh_tokens" ADD COLUMN "grant_id" uuid;--> statement-breakpoint +CREATE INDEX "oauth_access_grant_idx" ON "oauth_mcp_access_tokens" USING btree ("grant_id");--> statement-breakpoint +CREATE INDEX "oauth_refresh_grant_idx" ON "oauth_mcp_refresh_tokens" USING btree ("grant_id"); \ No newline at end of file diff --git a/drizzle/migrations/meta/0043_snapshot.json b/drizzle/migrations/meta/0043_snapshot.json new file mode 100644 index 000000000..9f69f4f0f --- /dev/null +++ b/drizzle/migrations/meta/0043_snapshot.json @@ -0,0 +1,10999 @@ +{ + "id": "efb17e11-0dcb-4e3c-be39-d266368c10e5", + "prevId": "a833fd20-cc30-4f35-ab72-0ac271d0d37f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "actor_id": { + "name": "actor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "audit_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_logs_actor_id_idx": { + "name": "audit_logs_actor_id_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_logs_action_idx": { + "name": "audit_logs_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_logs_target_type_idx": { + "name": "audit_logs_target_type_idx", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_logs_target_id_idx": { + "name": "audit_logs_target_id_idx", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_logs_created_at_idx": { + "name": "audit_logs_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_logs_actor_id_users_id_fk": { + "name": "audit_logs_actor_id_users_id_fk", + "tableFrom": "audit_logs", + "tableTo": "users", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.builder_project_events": { + "name": "builder_project_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revision_id": { + "name": "revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "sequence": { + "name": "sequence", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "client_event_id": { + "name": "client_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_mutation_id": { + "name": "client_mutation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "browser_session_id": { + "name": "browser_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "builder_project_event_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "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": { + "builder_project_events_project_created_at_idx": { + "name": "builder_project_events_project_created_at_idx", + "columns": [ + { + "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": {} + }, + "builder_project_events_run_sequence_idx": { + "name": "builder_project_events_run_sequence_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "builder_project_events_client_mutation_idx": { + "name": "builder_project_events_client_mutation_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_mutation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "builder_project_events_project_owner_fk": { + "name": "builder_project_events_project_owner_fk", + "tableFrom": "builder_project_events", + "tableTo": "builder_projects", + "columnsFrom": ["project_id", "owner_id"], + "columnsTo": ["id", "owner_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_events_thread_fk": { + "name": "builder_project_events_thread_fk", + "tableFrom": "builder_project_events", + "tableTo": "builder_project_threads", + "columnsFrom": ["project_id", "thread_id"], + "columnsTo": ["project_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_events_revision_fk": { + "name": "builder_project_events_revision_fk", + "tableFrom": "builder_project_events", + "tableTo": "builder_project_revisions", + "columnsFrom": ["project_id", "revision_id"], + "columnsTo": ["project_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_events_message_fk": { + "name": "builder_project_events_message_fk", + "tableFrom": "builder_project_events", + "tableTo": "builder_project_messages", + "columnsFrom": ["project_id", "message_id"], + "columnsTo": ["project_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_events_run_fk": { + "name": "builder_project_events_run_fk", + "tableFrom": "builder_project_events", + "tableTo": "builder_project_runs", + "columnsFrom": ["project_id", "run_id"], + "columnsTo": ["project_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "builder_project_events_project_sequence_unique": { + "name": "builder_project_events_project_sequence_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "sequence"] + }, + "builder_project_events_project_client_event_unique": { + "name": "builder_project_events_project_client_event_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "client_event_id"] + } + }, + "policies": {}, + "checkConstraints": { + "builder_project_events_sequence_check": { + "name": "builder_project_events_sequence_check", + "value": "\"builder_project_events\".\"sequence\" BETWEEN 1 AND 9007199254740991" + }, + "builder_project_events_payload_check": { + "name": "builder_project_events_payload_check", + "value": "jsonb_typeof(\"builder_project_events\".\"payload\") = 'object' AND pg_column_size(\"builder_project_events\".\"payload\") <= 262144" + }, + "builder_project_events_entity_reference_check": { + "name": "builder_project_events_entity_reference_check", + "value": "((\"builder_project_events\".\"type\"::text NOT LIKE 'revision.%') OR \"builder_project_events\".\"revision_id\" IS NOT NULL)\n AND ((\"builder_project_events\".\"type\"::text NOT LIKE 'thread.%') OR \"builder_project_events\".\"thread_id\" IS NOT NULL)\n AND ((\"builder_project_events\".\"type\"::text NOT LIKE 'message.%') OR \"builder_project_events\".\"message_id\" IS NOT NULL)\n AND ((\"builder_project_events\".\"type\"::text NOT LIKE 'run.%') OR \"builder_project_events\".\"run_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.builder_project_legacy_imports": { + "name": "builder_project_legacy_imports", + "schema": "", + "columns": { + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "builder_project_legacy_imports_owner_id_users_id_fk": { + "name": "builder_project_legacy_imports_owner_id_users_id_fk", + "tableFrom": "builder_project_legacy_imports", + "tableTo": "users", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.builder_project_messages": { + "name": "builder_project_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "client_mutation_id": { + "name": "client_mutation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "builder_message_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parts": { + "name": "parts", + "type": "jsonb", + "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": { + "builder_project_messages_project_thread_created_at_idx": { + "name": "builder_project_messages_project_thread_created_at_idx", + "columns": [ + { + "expression": "project_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": {} + }, + "builder_project_messages_run_id_idx": { + "name": "builder_project_messages_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "builder_project_messages_project_owner_fk": { + "name": "builder_project_messages_project_owner_fk", + "tableFrom": "builder_project_messages", + "tableTo": "builder_projects", + "columnsFrom": ["project_id", "owner_id"], + "columnsTo": ["id", "owner_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_messages_thread_fk": { + "name": "builder_project_messages_thread_fk", + "tableFrom": "builder_project_messages", + "tableTo": "builder_project_threads", + "columnsFrom": ["project_id", "thread_id"], + "columnsTo": ["project_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_messages_run_fk": { + "name": "builder_project_messages_run_fk", + "tableFrom": "builder_project_messages", + "tableTo": "builder_project_runs", + "columnsFrom": ["project_id", "thread_id", "run_id"], + "columnsTo": ["project_id", "thread_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "builder_project_messages_project_id_unique": { + "name": "builder_project_messages_project_id_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "id"] + }, + "builder_project_messages_project_mutation_unique": { + "name": "builder_project_messages_project_mutation_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "client_mutation_id"] + }, + "builder_project_messages_thread_position_unique": { + "name": "builder_project_messages_thread_position_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "thread_id", "position"] + } + }, + "policies": {}, + "checkConstraints": { + "builder_project_messages_parts_check": { + "name": "builder_project_messages_parts_check", + "value": "jsonb_typeof(\"builder_project_messages\".\"parts\") = 'array' AND pg_column_size(\"builder_project_messages\".\"parts\") <= 1048576" + }, + "builder_project_messages_content_size_check": { + "name": "builder_project_messages_content_size_check", + "value": "octet_length(\"builder_project_messages\".\"content\") <= 1048576" + }, + "builder_project_messages_position_check": { + "name": "builder_project_messages_position_check", + "value": "\"builder_project_messages\".\"position\" BETWEEN 1 AND 9007199254740991" + } + }, + "isRLSEnabled": false + }, + "public.builder_project_mutation_receipts": { + "name": "builder_project_mutation_receipts", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_mutation_id": { + "name": "client_mutation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "command_type": { + "name": "command_type", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "builder_project_mutation_receipts_project_fk": { + "name": "builder_project_mutation_receipts_project_fk", + "tableFrom": "builder_project_mutation_receipts", + "tableTo": "builder_projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "builder_project_mutation_receipts_pk": { + "name": "builder_project_mutation_receipts_pk", + "columns": ["project_id", "client_mutation_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "builder_project_mutation_receipts_command_type_check": { + "name": "builder_project_mutation_receipts_command_type_check", + "value": "\"builder_project_mutation_receipts\".\"command_type\" IN ('project.create', 'project.revise', 'project.delete', 'thread.create', 'run.enqueue', 'run.claim', 'run.cancel', 'run.finish', 'run.finish.fallback', 'transcript.import')" + }, + "builder_project_mutation_receipts_request_hash_check": { + "name": "builder_project_mutation_receipts_request_hash_check", + "value": "\"builder_project_mutation_receipts\".\"request_hash\" ~ '^[a-f0-9]{64}$'" + } + }, + "isRLSEnabled": false + }, + "public.builder_project_revisions": { + "name": "builder_project_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_mutation_id": { + "name": "client_mutation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_revision_id": { + "name": "parent_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "snapshot_hash": { + "name": "snapshot_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "builder_project_revisions_project_created_at_idx": { + "name": "builder_project_revisions_project_created_at_idx", + "columns": [ + { + "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": {} + }, + "builder_project_revisions_snapshot_hash_idx": { + "name": "builder_project_revisions_snapshot_hash_idx", + "columns": [ + { + "expression": "snapshot_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "builder_project_revisions_project_owner_fk": { + "name": "builder_project_revisions_project_owner_fk", + "tableFrom": "builder_project_revisions", + "tableTo": "builder_projects", + "columnsFrom": ["project_id", "owner_id"], + "columnsTo": ["id", "owner_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_revisions_parent_fk": { + "name": "builder_project_revisions_parent_fk", + "tableFrom": "builder_project_revisions", + "tableTo": "builder_project_revisions", + "columnsFrom": ["project_id", "parent_revision_id"], + "columnsTo": ["project_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_revisions_snapshot_hash_fk": { + "name": "builder_project_revisions_snapshot_hash_fk", + "tableFrom": "builder_project_revisions", + "tableTo": "builder_project_snapshots", + "columnsFrom": ["snapshot_hash"], + "columnsTo": ["hash"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "builder_project_revisions_project_revision_unique": { + "name": "builder_project_revisions_project_revision_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "revision_number"] + }, + "builder_project_revisions_project_id_unique": { + "name": "builder_project_revisions_project_id_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "id"] + }, + "builder_project_revisions_project_mutation_unique": { + "name": "builder_project_revisions_project_mutation_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "client_mutation_id"] + } + }, + "policies": {}, + "checkConstraints": { + "builder_project_revisions_snapshot_hash_check": { + "name": "builder_project_revisions_snapshot_hash_check", + "value": "\"builder_project_revisions\".\"snapshot_hash\" ~ '^[a-f0-9]{64}$'" + }, + "builder_project_revisions_revision_number_check": { + "name": "builder_project_revisions_revision_number_check", + "value": "\"builder_project_revisions\".\"revision_number\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.builder_project_runs": { + "name": "builder_project_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_mutation_id": { + "name": "client_mutation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "builder_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queue_kind": { + "name": "queue_kind", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'queue'" + }, + "provider": { + "name": "provider", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "base_revision_id": { + "name": "base_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "result_revision_id": { + "name": "result_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_owner_id": { + "name": "lease_owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_fencing_token": { + "name": "lease_fencing_token", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "activity": { + "name": "activity", + "type": "jsonb", + "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": { + "builder_project_runs_project_thread_created_at_idx": { + "name": "builder_project_runs_project_thread_created_at_idx", + "columns": [ + { + "expression": "project_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": {} + }, + "builder_project_runs_active_lease_idx": { + "name": "builder_project_runs_active_lease_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "builder_project_runs_active_project_unique": { + "name": "builder_project_runs_active_project_unique", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"builder_project_runs\".\"status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "builder_project_runs_project_owner_fk": { + "name": "builder_project_runs_project_owner_fk", + "tableFrom": "builder_project_runs", + "tableTo": "builder_projects", + "columnsFrom": ["project_id", "owner_id"], + "columnsTo": ["id", "owner_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_runs_thread_fk": { + "name": "builder_project_runs_thread_fk", + "tableFrom": "builder_project_runs", + "tableTo": "builder_project_threads", + "columnsFrom": ["project_id", "thread_id"], + "columnsTo": ["project_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_runs_base_revision_fk": { + "name": "builder_project_runs_base_revision_fk", + "tableFrom": "builder_project_runs", + "tableTo": "builder_project_revisions", + "columnsFrom": ["project_id", "base_revision_id"], + "columnsTo": ["project_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_runs_result_revision_fk": { + "name": "builder_project_runs_result_revision_fk", + "tableFrom": "builder_project_runs", + "tableTo": "builder_project_revisions", + "columnsFrom": ["project_id", "result_revision_id"], + "columnsTo": ["project_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "builder_project_runs_project_id_unique": { + "name": "builder_project_runs_project_id_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "id"] + }, + "builder_project_runs_project_thread_id_unique": { + "name": "builder_project_runs_project_thread_id_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "thread_id", "id"] + }, + "builder_project_runs_project_mutation_unique": { + "name": "builder_project_runs_project_mutation_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "client_mutation_id"] + } + }, + "policies": {}, + "checkConstraints": { + "builder_project_runs_queue_kind_check": { + "name": "builder_project_runs_queue_kind_check", + "value": "\"builder_project_runs\".\"queue_kind\" IN ('queue', 'steer')" + }, + "builder_project_runs_fencing_token_check": { + "name": "builder_project_runs_fencing_token_check", + "value": "\"builder_project_runs\".\"lease_fencing_token\" BETWEEN 0 AND 9007199254740991" + }, + "builder_project_runs_lease_pair_check": { + "name": "builder_project_runs_lease_pair_check", + "value": "(\"builder_project_runs\".\"lease_owner_id\" IS NULL) = (\"builder_project_runs\".\"lease_expires_at\" IS NULL)" + }, + "builder_project_runs_running_lease_check": { + "name": "builder_project_runs_running_lease_check", + "value": "(\"builder_project_runs\".\"status\" = 'running') = (\"builder_project_runs\".\"lease_owner_id\" IS NOT NULL)" + }, + "builder_project_runs_terminal_state_check": { + "name": "builder_project_runs_terminal_state_check", + "value": "(\"builder_project_runs\".\"status\" IN ('interrupted', 'completed', 'failed', 'cancelled')) = (\"builder_project_runs\".\"completed_at\" IS NOT NULL)" + }, + "builder_project_runs_timestamp_order_check": { + "name": "builder_project_runs_timestamp_order_check", + "value": "\"builder_project_runs\".\"completed_at\" IS NULL OR \"builder_project_runs\".\"started_at\" IS NULL OR \"builder_project_runs\".\"completed_at\" >= \"builder_project_runs\".\"started_at\"" + }, + "builder_project_runs_activity_size_check": { + "name": "builder_project_runs_activity_size_check", + "value": "\"builder_project_runs\".\"activity\" IS NULL OR (jsonb_typeof(\"builder_project_runs\".\"activity\") = 'object' AND pg_column_size(\"builder_project_runs\".\"activity\") <= 262144)" + }, + "builder_project_runs_error_size_check": { + "name": "builder_project_runs_error_size_check", + "value": "\"builder_project_runs\".\"error\" IS NULL OR (jsonb_typeof(\"builder_project_runs\".\"error\") = 'object' AND pg_column_size(\"builder_project_runs\".\"error\") <= 262144)" + }, + "builder_project_runs_result_revision_check": { + "name": "builder_project_runs_result_revision_check", + "value": "\"builder_project_runs\".\"result_revision_id\" IS NULL OR \"builder_project_runs\".\"status\" = 'completed'" + } + }, + "isRLSEnabled": false + }, + "public.builder_project_snapshot_reservations": { + "name": "builder_project_snapshot_reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_hash": { + "name": "snapshot_hash", + "type": "varchar(64)", + "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": { + "builder_project_snapshot_reservations_owner_created_at_idx": { + "name": "builder_project_snapshot_reservations_owner_created_at_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "builder_project_snapshot_reservations_snapshot_hash_idx": { + "name": "builder_project_snapshot_reservations_snapshot_hash_idx", + "columns": [ + { + "expression": "snapshot_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "builder_project_snapshot_reservations_owner_id_users_id_fk": { + "name": "builder_project_snapshot_reservations_owner_id_users_id_fk", + "tableFrom": "builder_project_snapshot_reservations", + "tableTo": "users", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "builder_project_snapshot_reservations_snapshot_hash_builder_project_snapshots_hash_fk": { + "name": "builder_project_snapshot_reservations_snapshot_hash_builder_project_snapshots_hash_fk", + "tableFrom": "builder_project_snapshot_reservations", + "tableTo": "builder_project_snapshots", + "columnsFrom": ["snapshot_hash"], + "columnsTo": ["hash"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "builder_project_snapshot_reservations_owner_snapshot_unique": { + "name": "builder_project_snapshot_reservations_owner_snapshot_unique", + "nullsNotDistinct": false, + "columns": ["owner_id", "snapshot_hash"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.builder_project_snapshots": { + "name": "builder_project_snapshots", + "schema": "", + "columns": { + "hash": { + "name": "hash", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "source_bytes": { + "name": "source_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "stored_at": { + "name": "stored_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleting_at": { + "name": "deleting_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "quarantined_at": { + "name": "quarantined_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": { + "builder_project_snapshots_stored_at_idx": { + "name": "builder_project_snapshots_stored_at_idx", + "columns": [ + { + "expression": "stored_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "builder_project_snapshots_hash_check": { + "name": "builder_project_snapshots_hash_check", + "value": "\"builder_project_snapshots\".\"hash\" ~ '^[a-f0-9]{64}$'" + }, + "builder_project_snapshots_source_bytes_check": { + "name": "builder_project_snapshots_source_bytes_check", + "value": "\"builder_project_snapshots\".\"source_bytes\" IS NULL OR \"builder_project_snapshots\".\"source_bytes\" BETWEEN 1 AND 1048576" + } + }, + "isRLSEnabled": false + }, + "public.builder_project_threads": { + "name": "builder_project_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_mutation_id": { + "name": "client_mutation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "last_message_position": { + "name": "last_message_position", + "type": "bigint", + "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()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "builder_project_threads_project_updated_at_idx": { + "name": "builder_project_threads_project_updated_at_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "builder_project_threads_project_owner_fk": { + "name": "builder_project_threads_project_owner_fk", + "tableFrom": "builder_project_threads", + "tableTo": "builder_projects", + "columnsFrom": ["project_id", "owner_id"], + "columnsTo": ["id", "owner_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "builder_project_threads_project_id_unique": { + "name": "builder_project_threads_project_id_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "id"] + }, + "builder_project_threads_project_mutation_unique": { + "name": "builder_project_threads_project_mutation_unique", + "nullsNotDistinct": false, + "columns": ["project_id", "client_mutation_id"] + } + }, + "policies": {}, + "checkConstraints": { + "builder_project_threads_message_position_check": { + "name": "builder_project_threads_message_position_check", + "value": "\"builder_project_threads\".\"last_message_position\" BETWEEN 0 AND 9007199254740991" + } + }, + "isRLSEnabled": false + }, + "public.builder_project_tombstones": { + "name": "builder_project_tombstones", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "builder_project_tombstones_owner_deleted_at_idx": { + "name": "builder_project_tombstones_owner_deleted_at_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "builder_project_tombstones_owner_id_users_id_fk": { + "name": "builder_project_tombstones_owner_id_users_id_fk", + "tableFrom": "builder_project_tombstones", + "tableTo": "users", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.builder_project_usage": { + "name": "builder_project_usage", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "threads": { + "name": "threads", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "messages": { + "name": "messages", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "runs": { + "name": "runs", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "revisions": { + "name": "revisions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "events": { + "name": "events", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "payload_bytes": { + "name": "payload_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "builder_project_usage_project_id_builder_projects_id_fk": { + "name": "builder_project_usage_project_id_builder_projects_id_fk", + "tableFrom": "builder_project_usage", + "tableTo": "builder_projects", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "builder_project_usage_nonnegative_check": { + "name": "builder_project_usage_nonnegative_check", + "value": "\"builder_project_usage\".\"threads\" >= 0 AND \"builder_project_usage\".\"messages\" >= 0 AND \"builder_project_usage\".\"runs\" >= 0 AND \"builder_project_usage\".\"revisions\" >= 0 AND \"builder_project_usage\".\"events\" >= 0 AND \"builder_project_usage\".\"payload_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.builder_projects": { + "name": "builder_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_mutation_id": { + "name": "client_mutation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "forked_from_id": { + "name": "forked_from_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "snapshot_hash": { + "name": "snapshot_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "current_revision_number": { + "name": "current_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_event_sequence": { + "name": "last_event_sequence", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_lease_fencing_token": { + "name": "last_lease_fencing_token", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by_id": { + "name": "deleted_by_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": { + "builder_projects_owner_id_idx": { + "name": "builder_projects_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "builder_projects_owner_updated_at_idx": { + "name": "builder_projects_owner_updated_at_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "builder_projects_deleted_at_idx": { + "name": "builder_projects_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "builder_projects_owner_id_users_id_fk": { + "name": "builder_projects_owner_id_users_id_fk", + "tableFrom": "builder_projects", + "tableTo": "users", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "builder_projects_deleted_by_id_users_id_fk": { + "name": "builder_projects_deleted_by_id_users_id_fk", + "tableFrom": "builder_projects", + "tableTo": "users", + "columnsFrom": ["deleted_by_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "builder_projects_forked_from_fk": { + "name": "builder_projects_forked_from_fk", + "tableFrom": "builder_projects", + "tableTo": "builder_projects", + "columnsFrom": ["forked_from_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "builder_projects_snapshot_hash_fk": { + "name": "builder_projects_snapshot_hash_fk", + "tableFrom": "builder_projects", + "tableTo": "builder_project_snapshots", + "columnsFrom": ["snapshot_hash"], + "columnsTo": ["hash"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "builder_projects_id_owner_unique": { + "name": "builder_projects_id_owner_unique", + "nullsNotDistinct": false, + "columns": ["id", "owner_id"] + }, + "builder_projects_owner_mutation_unique": { + "name": "builder_projects_owner_mutation_unique", + "nullsNotDistinct": false, + "columns": ["owner_id", "client_mutation_id"] + } + }, + "policies": {}, + "checkConstraints": { + "builder_projects_snapshot_hash_check": { + "name": "builder_projects_snapshot_hash_check", + "value": "\"builder_projects\".\"snapshot_hash\" ~ '^[a-f0-9]{64}$'" + }, + "builder_projects_revision_number_check": { + "name": "builder_projects_revision_number_check", + "value": "\"builder_projects\".\"current_revision_number\" > 0" + }, + "builder_projects_event_sequence_check": { + "name": "builder_projects_event_sequence_check", + "value": "\"builder_projects\".\"last_event_sequence\" BETWEEN 0 AND 9007199254740991" + }, + "builder_projects_lease_fencing_token_check": { + "name": "builder_projects_lease_fencing_token_check", + "value": "\"builder_projects\".\"last_lease_fencing_token\" BETWEEN 0 AND 9007199254740991" + } + }, + "isRLSEnabled": false + }, + "public.chat_access": { + "name": "chat_access", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_access_user_id_users_id_fk": { + "name": "chat_access_user_id_users_id_fk", + "tableFrom": "chat_access", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_access_invited_by_users_id_fk": { + "name": "chat_access_invited_by_users_id_fk", + "tableFrom": "chat_access", + "tableTo": "users", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_account_onboarding": { + "name": "chat_account_onboarding", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "use_case": { + "name": "use_case", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "chat_account_onboarding_user_id_users_id_fk": { + "name": "chat_account_onboarding_user_id_users_id_fk", + "tableFrom": "chat_account_onboarding", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_account_onboarding_commands": { + "name": "chat_account_onboarding_commands", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "command_id": { + "name": "command_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_digest": { + "name": "request_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mutation_id": { + "name": "mutation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "result_json": { + "name": "result_json", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_account_onboarding_commands_user_id_chat_account_onboarding_user_id_fk": { + "name": "chat_account_onboarding_commands_user_id_chat_account_onboarding_user_id_fk", + "tableFrom": "chat_account_onboarding_commands", + "tableTo": "chat_account_onboarding", + "columnsFrom": ["user_id"], + "columnsTo": ["user_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_account_onboarding_commands_user_id_command_id_pk": { + "name": "chat_account_onboarding_commands_user_id_command_id_pk", + "columns": ["user_id", "command_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_account_preferences": { + "name": "chat_account_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "timezone_confirmed_at": { + "name": "timezone_confirmed_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "response": { + "name": "response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "appearance": { + "name": "appearance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "chat_account_preferences_user_id_users_id_fk": { + "name": "chat_account_preferences_user_id_users_id_fk", + "tableFrom": "chat_account_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_preferences_revision_safe": { + "name": "chat_preferences_revision_safe", + "value": "\"chat_account_preferences\".\"revision\" >= 0 AND \"chat_account_preferences\".\"revision\" <= 9007199254740991" + } + }, + "isRLSEnabled": false + }, + "public.chat_bot_drafts": { + "name": "chat_bot_drafts", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "started_at": { + "name": "started_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "run_model": { + "name": "run_model", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reference_inputs": { + "name": "reference_inputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "file_ids": { + "name": "file_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_bot_draft_bot_idx": { + "name": "chat_bot_draft_bot_idx", + "columns": [ + { + "expression": "bot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_bot_draft_scope_fk": { + "name": "chat_bot_draft_scope_fk", + "tableFrom": "chat_bot_drafts", + "tableTo": "chat_file_drafts", + "columnsFrom": ["workspace_id", "user_id", "id"], + "columnsTo": ["workspace_id", "user_id", "id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_bot_draft_conversation_fk": { + "name": "chat_bot_draft_conversation_fk", + "tableFrom": "chat_bot_drafts", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id", "bot_id", "user_id"], + "columnsTo": ["id", "bot_id", "user_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_bot_draft_workspace_fk": { + "name": "chat_bot_draft_workspace_fk", + "tableFrom": "chat_bot_drafts", + "tableTo": "chat_bots", + "columnsFrom": ["workspace_id", "bot_id"], + "columnsTo": ["workspace_id", "id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_bot_drafts_workspace_id_user_id_id_pk": { + "name": "chat_bot_drafts_workspace_id_user_id_id_pk", + "columns": ["workspace_id", "user_id", "id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_bot_draft_attachments": { + "name": "chat_bot_draft_attachments", + "value": "jsonb_typeof(\"chat_bot_drafts\".\"file_ids\")='array' AND jsonb_array_length(\"chat_bot_drafts\".\"file_ids\")<=5" + } + }, + "isRLSEnabled": false + }, + "public.chat_bot_schedule_suspensions": { + "name": "chat_bot_schedule_suspensions", + "schema": "", + "columns": { + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "chat_bot_schedule_suspensions_bot_id_chat_bots_id_fk": { + "name": "chat_bot_schedule_suspensions_bot_id_chat_bots_id_fk", + "tableFrom": "chat_bot_schedule_suspensions", + "tableTo": "chat_bots", + "columnsFrom": ["bot_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_bot_generation_safe": { + "name": "chat_bot_generation_safe", + "value": "\"chat_bot_schedule_suspensions\".\"generation\" BETWEEN 0 AND 9007199254740991" + } + }, + "isRLSEnabled": false + }, + "public.chat_bot_sections": { + "name": "chat_bot_sections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sort_override": { + "name": "sort_override", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chat_bot_sections_viewer": { + "name": "chat_bot_sections_viewer", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_bot_sections_workspace_id_chat_workspaces_id_fk": { + "name": "chat_bot_sections_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_bot_sections", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_bot_sections_user_id_users_id_fk": { + "name": "chat_bot_sections_user_id_users_id_fk", + "tableFrom": "chat_bot_sections", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_bot_viewer_state": { + "name": "chat_bot_viewer_state", + "schema": "", + "columns": { + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "section_id": { + "name": "section_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_bot_viewer_state_bot_id_chat_bots_id_fk": { + "name": "chat_bot_viewer_state_bot_id_chat_bots_id_fk", + "tableFrom": "chat_bot_viewer_state", + "tableTo": "chat_bots", + "columnsFrom": ["bot_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_bot_viewer_state_user_id_users_id_fk": { + "name": "chat_bot_viewer_state_user_id_users_id_fk", + "tableFrom": "chat_bot_viewer_state", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_bot_viewer_state_section_id_chat_bot_sections_id_fk": { + "name": "chat_bot_viewer_state_section_id_chat_bot_sections_id_fk", + "tableFrom": "chat_bot_viewer_state", + "tableTo": "chat_bot_sections", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_bot_viewer_state_bot_id_user_id_pk": { + "name": "chat_bot_viewer_state_bot_id_user_id_pk", + "columns": ["bot_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_bots": { + "name": "chat_bots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deletion_batch": { + "name": "deletion_batch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_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_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_bots_workspace_idx": { + "name": "chat_bots_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_bots_workspace_id_chat_workspaces_id_fk": { + "name": "chat_bots_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_bots", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_bots_workspace_id_parent_id_chat_bots_workspace_id_id_fk": { + "name": "chat_bots_workspace_id_parent_id_chat_bots_workspace_id_id_fk", + "tableFrom": "chat_bots", + "tableTo": "chat_bots", + "columnsFrom": ["workspace_id", "parent_id"], + "columnsTo": ["workspace_id", "id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_bots_workspace_id_unique": { + "name": "chat_bots_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id", "id"] + } + }, + "policies": {}, + "checkConstraints": { + "chat_personal_assistant_active_check": { + "name": "chat_personal_assistant_active_check", + "value": "\"chat_bots\".\"id\" NOT LIKE 'assistant:%' OR (\"chat_bots\".\"archived_at\" IS NULL AND \"chat_bots\".\"deleted_at\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.chat_composer_drafts": { + "name": "chat_composer_drafts", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_composer_drafts_user_id_users_id_fk": { + "name": "chat_composer_drafts_user_id_users_id_fk", + "tableFrom": "chat_composer_drafts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_composer_drafts_user_id_scope_pk": { + "name": "chat_composer_drafts_user_id_scope_pk", + "columns": ["user_id", "scope"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_draft_revision_safe": { + "name": "chat_draft_revision_safe", + "value": "\"chat_composer_drafts\".\"revision\">0 AND \"chat_composer_drafts\".\"revision\"<=9007199254740991" + } + }, + "isRLSEnabled": false + }, + "public.chat_connected_devices": { + "name": "chat_connected_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_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 + }, + "grants": { + "name": "grants", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen": { + "name": "last_seen", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "revoked_at": { + "name": "revoked_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_devices_owner": { + "name": "chat_devices_owner", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_connected_devices_user_id_users_id_fk": { + "name": "chat_connected_devices_user_id_users_id_fk", + "tableFrom": "chat_connected_devices", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_connected_devices_token_hash_unique": { + "name": "chat_connected_devices_token_hash_unique", + "nullsNotDistinct": false, + "columns": ["token_hash"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_conversation_activity": { + "name": "chat_conversation_activity", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "activity_at": { + "name": "activity_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "event_version": { + "name": "event_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "read_version": { + "name": "read_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "preview": { + "name": "preview", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "message_count": { + "name": "message_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "queued_count": { + "name": "queued_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "queue_paused": { + "name": "queue_paused", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "chat_activity_user_time": { + "name": "chat_activity_user_time", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "activity_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_conversation_activity_conversation_id_chat_conversations_id_fk": { + "name": "chat_conversation_activity_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_conversation_activity", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversation_activity_bot_id_chat_bots_id_fk": { + "name": "chat_conversation_activity_bot_id_chat_bots_id_fk", + "tableFrom": "chat_conversation_activity", + "tableTo": "chat_bots", + "columnsFrom": ["bot_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversation_activity_user_id_users_id_fk": { + "name": "chat_conversation_activity_user_id_users_id_fk", + "tableFrom": "chat_conversation_activity", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_activity_status": { + "name": "chat_activity_status", + "value": "\"chat_conversation_activity\".\"status\" IN ('idle','running','approval','setup','error','completed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_conversation_copies": { + "name": "chat_conversation_copies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "retry_id": { + "name": "retry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_bot_id": { + "name": "source_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_conversation_id": { + "name": "source_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_bot_id": { + "name": "target_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_conversation_id": { + "name": "target_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_digest": { + "name": "request_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "boundary_json": { + "name": "boundary_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'copying'" + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'export'" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "next_page": { + "name": "next_page", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "work_version": { + "name": "work_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "retry_at": { + "name": "retry_at", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chat_copies_pending": { + "name": "chat_copies_pending", + "columns": [ + { + "expression": "source_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "phase", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_copies_viewer": { + "name": "chat_copies_viewer", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_conversation_copies_retry_id_chat_conversation_retries_id_fk": { + "name": "chat_conversation_copies_retry_id_chat_conversation_retries_id_fk", + "tableFrom": "chat_conversation_copies", + "tableTo": "chat_conversation_retries", + "columnsFrom": ["retry_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_conversation_copies_workspace_id_chat_workspaces_id_fk": { + "name": "chat_conversation_copies_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_conversation_copies", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_conversation_copies_user_id_users_id_fk": { + "name": "chat_conversation_copies_user_id_users_id_fk", + "tableFrom": "chat_conversation_copies", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_conversation_copies_retry_id_unique": { + "name": "chat_conversation_copies_retry_id_unique", + "nullsNotDistinct": false, + "columns": ["retry_id"] + }, + "chat_conversation_copies_target_bot_id_unique": { + "name": "chat_conversation_copies_target_bot_id_unique", + "nullsNotDistinct": false, + "columns": ["target_bot_id"] + }, + "chat_conversation_copies_target_conversation_id_unique": { + "name": "chat_conversation_copies_target_conversation_id_unique", + "nullsNotDistinct": false, + "columns": ["target_conversation_id"] + }, + "chat_conversation_copies_workspace_id_user_id_idempotency_key_unique": { + "name": "chat_conversation_copies_workspace_id_user_id_idempotency_key_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id", "user_id", "idempotency_key"] + } + }, + "policies": {}, + "checkConstraints": { + "chat_copy_kind": { + "name": "chat_copy_kind", + "value": "\"chat_conversation_copies\".\"kind\" IN ('duplicate','fork')" + }, + "chat_copy_status": { + "name": "chat_copy_status", + "value": "\"chat_conversation_copies\".\"status\" IN ('copying','ready','failed')" + }, + "chat_copy_phase": { + "name": "chat_copy_phase", + "value": "\"chat_conversation_copies\".\"phase\" IN ('export','transfer','import','publish','cleanup','done')" + } + }, + "isRLSEnabled": false + }, + "public.chat_conversation_mains": { + "name": "chat_conversation_mains", + "schema": "", + "columns": { + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_conversation_mains_conversation_id_bot_id_user_id_chat_conversations_id_bot_id_user_id_fk": { + "name": "chat_conversation_mains_conversation_id_bot_id_user_id_chat_conversations_id_bot_id_user_id_fk", + "tableFrom": "chat_conversation_mains", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id", "bot_id", "user_id"], + "columnsTo": ["id", "bot_id", "user_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_conversation_mains_bot_id_user_id_pk": { + "name": "chat_conversation_mains_bot_id_user_id_pk", + "columns": ["bot_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_conversation_retries": { + "name": "chat_conversation_retries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_bot_id": { + "name": "source_bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_conversation_id": { + "name": "source_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_json": { + "name": "source_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "file_plan_json": { + "name": "file_plan_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'preparing'" + }, + "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": "bigint", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_retries_viewer": { + "name": "chat_retries_viewer", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_conversation_retries_workspace_id_chat_workspaces_id_fk": { + "name": "chat_conversation_retries_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_conversation_retries", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_conversation_retries_user_id_users_id_fk": { + "name": "chat_conversation_retries_user_id_users_id_fk", + "tableFrom": "chat_conversation_retries", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_conversation_retries_workspace_id_user_id_idempotency_key_unique": { + "name": "chat_conversation_retries_workspace_id_user_id_idempotency_key_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id", "user_id", "idempotency_key"] + } + }, + "policies": {}, + "checkConstraints": { + "chat_retry_status": { + "name": "chat_retry_status", + "value": "\"chat_conversation_retries\".\"status\" IN ('preparing','ready','failed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_conversation_threads": { + "name": "chat_conversation_threads", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "parent_conversation_id": { + "name": "parent_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_message_id": { + "name": "source_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chat_thread_parent_idx": { + "name": "chat_thread_parent_idx", + "columns": [ + { + "expression": "parent_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_thread_child_identity_fk": { + "name": "chat_thread_child_identity_fk", + "tableFrom": "chat_conversation_threads", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id", "bot_id", "user_id"], + "columnsTo": ["id", "bot_id", "user_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_thread_parent_identity_fk": { + "name": "chat_thread_parent_identity_fk", + "tableFrom": "chat_conversation_threads", + "tableTo": "chat_conversations", + "columnsFrom": ["parent_conversation_id", "bot_id", "user_id"], + "columnsTo": ["id", "bot_id", "user_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_thread_not_self": { + "name": "chat_thread_not_self", + "value": "\"chat_conversation_threads\".\"conversation_id\" <> \"chat_conversation_threads\".\"parent_conversation_id\"" + }, + "chat_thread_version_nonnegative": { + "name": "chat_thread_version_nonnegative", + "value": "\"chat_conversation_threads\".\"version\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.chat_conversations": { + "name": "chat_conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_conversations_bot_id_chat_bots_id_fk": { + "name": "chat_conversations_bot_id_chat_bots_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "chat_bots", + "columnsFrom": ["bot_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_conversations_user_id_users_id_fk": { + "name": "chat_conversations_user_id_users_id_fk", + "tableFrom": "chat_conversations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_conversation_identity": { + "name": "chat_conversation_identity", + "nullsNotDistinct": false, + "columns": ["id", "bot_id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_credentials": { + "name": "chat_credentials", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "ciphertext": { + "name": "ciphertext", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_credentials_user_id_users_id_fk": { + "name": "chat_credentials_user_id_users_id_fk", + "tableFrom": "chat_credentials", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_daily_usage": { + "name": "chat_daily_usage", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "turns": { + "name": "turns", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "chat_daily_usage_user_id_day_pk": { + "name": "chat_daily_usage_user_id_day_pk", + "columns": ["user_id", "day"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_device_operations": { + "name": "chat_device_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request": { + "name": "request", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_device_operations_pending": { + "name": "chat_device_operations_pending", + "columns": [ + { + "expression": "device_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": {} + } + }, + "foreignKeys": { + "chat_device_operations_device_id_chat_connected_devices_id_fk": { + "name": "chat_device_operations_device_id_chat_connected_devices_id_fk", + "tableFrom": "chat_device_operations", + "tableTo": "chat_connected_devices", + "columnsFrom": ["device_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_device_operations_user_id_users_id_fk": { + "name": "chat_device_operations_user_id_users_id_fk", + "tableFrom": "chat_device_operations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_device_operations_conversation_id_chat_conversations_id_fk": { + "name": "chat_device_operations_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_device_operations", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_execution_membership_generations": { + "name": "chat_execution_membership_generations", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_execution_membership_generations_workspace_id_chat_workspaces_id_fk": { + "name": "chat_execution_membership_generations_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_execution_membership_generations", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_execution_membership_generations_user_id_users_id_fk": { + "name": "chat_execution_membership_generations_user_id_users_id_fk", + "tableFrom": "chat_execution_membership_generations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_execution_membership_generations_workspace_id_user_id_pk": { + "name": "chat_execution_membership_generations_workspace_id_user_id_pk", + "columns": ["workspace_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_membership_generation_safe": { + "name": "chat_membership_generation_safe", + "value": "\"chat_execution_membership_generations\".\"generation\" BETWEEN 0 AND 9007199254740991" + } + }, + "isRLSEnabled": false + }, + "public.chat_file_drafts": { + "name": "chat_file_drafts", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_file_drafts_workspace_id_chat_workspaces_id_fk": { + "name": "chat_file_drafts_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_file_drafts", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_file_drafts_user_id_users_id_fk": { + "name": "chat_file_drafts_user_id_users_id_fk", + "tableFrom": "chat_file_drafts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_file_drafts_workspace_id_user_id_id_pk": { + "name": "chat_file_drafts_workspace_id_user_id_id_pk", + "columns": ["workspace_id", "user_id", "id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_funded_spend": { + "name": "chat_funded_spend", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reserved_micros": { + "name": "reserved_micros", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "billed_micros": { + "name": "billed_micros", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "observed_micros": { + "name": "observed_micros", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "unknown": { + "name": "unknown", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "chat_funded_spend_day_user": { + "name": "chat_funded_spend_day_user", + "columns": [ + { + "expression": "day", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_funded_spend_conversation_id_chat_conversations_id_fk": { + "name": "chat_funded_spend_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_funded_spend", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_funded_spend_user_id_users_id_fk": { + "name": "chat_funded_spend_user_id_users_id_fk", + "tableFrom": "chat_funded_spend", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_funded_spend_conversation_id_run_id_pk": { + "name": "chat_funded_spend_conversation_id_run_id_pk", + "columns": ["conversation_id", "run_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_invites": { + "name": "chat_invites", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "redeemed_by": { + "name": "redeemed_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "redeemed_at": { + "name": "redeemed_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": { + "chat_invites_creator_idx": { + "name": "chat_invites_creator_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_invites_created_by_users_id_fk": { + "name": "chat_invites_created_by_users_id_fk", + "tableFrom": "chat_invites", + "tableTo": "users", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_invites_redeemed_by_users_id_fk": { + "name": "chat_invites_redeemed_by_users_id_fk", + "tableFrom": "chat_invites", + "tableTo": "users", + "columnsFrom": ["redeemed_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_kody_account_probe": { + "name": "chat_kody_account_probe", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "account_fingerprint": { + "name": "account_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_fingerprint": { + "name": "source_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "checked_at": { + "name": "checked_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_kody_account_probe_user_id_users_id_fk": { + "name": "chat_kody_account_probe_user_id_users_id_fk", + "tableFrom": "chat_kody_account_probe", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_kody_account_reference_catalog": { + "name": "chat_kody_account_reference_catalog", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "account_fingerprint": { + "name": "account_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fetched_at": { + "name": "fetched_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_kody_account_reference_catalog_user_id_users_id_fk": { + "name": "chat_kody_account_reference_catalog_user_id_users_id_fk", + "tableFrom": "chat_kody_account_reference_catalog", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_kody_links": { + "name": "chat_kody_links", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_kody_links_user_id_users_id_fk": { + "name": "chat_kody_links_user_id_users_id_fk", + "tableFrom": "chat_kody_links", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_kody_links_subject_unique": { + "name": "chat_kody_links_subject_unique", + "nullsNotDistinct": false, + "columns": ["subject"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_kody_oauth_clients": { + "name": "chat_kody_oauth_clients", + "schema": "", + "columns": { + "origin": { + "name": "origin", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_kody_oauth_pending": { + "name": "chat_kody_oauth_pending", + "schema": "", + "columns": { + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_kody_oauth_pending_expiry": { + "name": "chat_kody_oauth_pending_expiry", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_kody_oauth_pending_user_id_users_id_fk": { + "name": "chat_kody_oauth_pending_user_id_users_id_fk", + "tableFrom": "chat_kody_oauth_pending", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_kody_reference_catalog": { + "name": "chat_kody_reference_catalog", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "account_fingerprint": { + "name": "account_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fetched_at": { + "name": "fetched_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "complete": { + "name": "complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "data_revision": { + "name": "data_revision", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "refresh_started_at": { + "name": "refresh_started_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_kody_reference_catalog_user_id_users_id_fk": { + "name": "chat_kody_reference_catalog_user_id_users_id_fk", + "tableFrom": "chat_kody_reference_catalog", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_kody_reference_items": { + "name": "chat_kody_reference_items", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_kody_reference_items_user_id_users_id_fk": { + "name": "chat_kody_reference_items_user_id_users_id_fk", + "tableFrom": "chat_kody_reference_items", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_kody_reference_items_user_id_revision_ordinal_pk": { + "name": "chat_kody_reference_items_user_id_revision_ordinal_pk", + "columns": ["user_id", "revision", "ordinal"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_kody_refresh_claims": { + "name": "chat_kody_refresh_claims", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "token_fingerprint": { + "name": "token_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "claim_id": { + "name": "claim_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lease_until": { + "name": "lease_until", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_kody_refresh_claims_user_id_users_id_fk": { + "name": "chat_kody_refresh_claims_user_id_users_id_fk", + "tableFrom": "chat_kody_refresh_claims", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_kody_refresh_status": { + "name": "chat_kody_refresh_status", + "value": "\"chat_kody_refresh_claims\".\"status\" IN ('refreshing','completed','needs_auth')" + } + }, + "isRLSEnabled": false + }, + "public.chat_kody_skill_sync": { + "name": "chat_kody_skill_sync", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "account_fingerprint": { + "name": "account_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fetched_at": { + "name": "fetched_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_until": { + "name": "lease_until", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "chat_kody_skill_sync_user_id_users_id_fk": { + "name": "chat_kody_skill_sync_user_id_users_id_fk", + "tableFrom": "chat_kody_skill_sync", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_kody_skill_sync_stage": { + "name": "chat_kody_skill_sync_stage", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_kody_skill_sync_stage_user_id_users_id_fk": { + "name": "chat_kody_skill_sync_stage_user_id_users_id_fk", + "tableFrom": "chat_kody_skill_sync_stage", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_kody_skill_sync_stage_user_id_revision_id_pk": { + "name": "chat_kody_skill_sync_stage_user_id_revision_id_pk", + "columns": ["user_id", "revision", "id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_kody_skill_versions": { + "name": "chat_kody_skill_versions", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "package_id": { + "name": "package_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_skill_id": { + "name": "source_skill_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document": { + "name": "document", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "current": { + "name": "current", + "type": "boolean", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_kody_skill_current_idx": { + "name": "chat_kody_skill_current_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_kody_skill_versions_user_id_users_id_fk": { + "name": "chat_kody_skill_versions_user_id_users_id_fk", + "tableFrom": "chat_kody_skill_versions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_kody_skill_versions_user_id_id_version_pk": { + "name": "chat_kody_skill_versions_user_id_id_version_pk", + "columns": ["user_id", "id", "version"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_mcp_account_commands": { + "name": "chat_mcp_account_commands", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "command_id": { + "name": "command_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt": { + "name": "receipt", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_mcp_account_commands_user_id_users_id_fk": { + "name": "chat_mcp_account_commands_user_id_users_id_fk", + "tableFrom": "chat_mcp_account_commands", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_mcp_account_commands_user_id_command_id_pk": { + "name": "chat_mcp_account_commands_user_id_command_id_pk", + "columns": ["user_id", "command_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_mcp_accounts": { + "name": "chat_mcp_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_mode": { + "name": "auth_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "removed": { + "name": "removed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_revision": { + "name": "token_revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "ciphertext": { + "name": "ciphertext", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checked_at": { + "name": "checked_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_claim": { + "name": "refresh_claim", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_until": { + "name": "refresh_until", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_mcp_owner": { + "name": "chat_mcp_owner", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "removed", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_mcp_accounts_user_id_users_id_fk": { + "name": "chat_mcp_accounts_user_id_users_id_fk", + "tableFrom": "chat_mcp_accounts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_mcp_auth_mode": { + "name": "chat_mcp_auth_mode", + "value": "\"chat_mcp_accounts\".\"auth_mode\" IN ('none','token','oauth')" + }, + "chat_mcp_status": { + "name": "chat_mcp_status", + "value": "\"chat_mcp_accounts\".\"status\" IN ('configured','checked','needs_auth','error')" + }, + "chat_mcp_revision": { + "name": "chat_mcp_revision", + "value": "\"chat_mcp_accounts\".\"revision\">0" + }, + "chat_mcp_token_revision": { + "name": "chat_mcp_token_revision", + "value": "\"chat_mcp_accounts\".\"token_revision\">=0" + } + }, + "isRLSEnabled": false + }, + "public.chat_mcp_reference_catalog": { + "name": "chat_mcp_reference_catalog", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fetched_at": { + "name": "fetched_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "refresh_started_at": { + "name": "refresh_started_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_mcp_reference_catalog_workspace_id_chat_workspaces_id_fk": { + "name": "chat_mcp_reference_catalog_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_mcp_reference_catalog", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_mcp_reference_catalog_user_id_users_id_fk": { + "name": "chat_mcp_reference_catalog_user_id_users_id_fk", + "tableFrom": "chat_mcp_reference_catalog", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_mcp_reference_catalog_workspace_id_user_id_server_id_pk": { + "name": "chat_mcp_reference_catalog_workspace_id_user_id_server_id_pk", + "columns": ["workspace_id", "user_id", "server_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_mcp_setup_attempts": { + "name": "chat_mcp_setup_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "ciphertext": { + "name": "ciphertext", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "browser_hash": { + "name": "browser_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_mcp_setup_owner": { + "name": "chat_mcp_setup_owner", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_mcp_setup_attempts_user_id_users_id_fk": { + "name": "chat_mcp_setup_attempts_user_id_users_id_fk", + "tableFrom": "chat_mcp_setup_attempts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_mcp_setup_attempts_workspace_id_chat_workspaces_id_fk": { + "name": "chat_mcp_setup_attempts_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_mcp_setup_attempts", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_mcp_setup_attempts_state_hash_unique": { + "name": "chat_mcp_setup_attempts_state_hash_unique", + "nullsNotDistinct": false, + "columns": ["state_hash"] + } + }, + "policies": {}, + "checkConstraints": { + "chat_mcp_setup_status": { + "name": "chat_mcp_setup_status", + "value": "\"chat_mcp_setup_attempts\".\"status\" IN ('review','starting','authorize','complete','failed')" + } + }, + "isRLSEnabled": false + }, + "public.chat_memberships": { + "name": "chat_memberships", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_memberships_user_idx": { + "name": "chat_memberships_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_memberships_workspace_id_chat_workspaces_id_fk": { + "name": "chat_memberships_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_memberships", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_memberships_user_id_users_id_fk": { + "name": "chat_memberships_user_id_users_id_fk", + "tableFrom": "chat_memberships", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_memberships_workspace_id_user_id_pk": { + "name": "chat_memberships_workspace_id_user_id_pk", + "columns": ["workspace_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_memberships_role_check": { + "name": "chat_memberships_role_check", + "value": "\"chat_memberships\".\"role\" IN ('owner', 'admin', 'member')" + } + }, + "isRLSEnabled": false + }, + "public.chat_memories": { + "name": "chat_memories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_message_id": { + "name": "source_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chat_memory_scope_idx": { + "name": "chat_memory_scope_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_memories_conversation_id_chat_conversations_id_fk": { + "name": "chat_memories_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_memories", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_memories_workspace_id_chat_workspaces_id_fk": { + "name": "chat_memories_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_memories", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_memories_user_id_users_id_fk": { + "name": "chat_memories_user_id_users_id_fk", + "tableFrom": "chat_memories", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_memory_commands": { + "name": "chat_memory_commands", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command_id": { + "name": "command_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "memory_id": { + "name": "memory_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_memory_command_identity_idx": { + "name": "chat_memory_command_identity_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_memory_commands_conversation_id_chat_conversations_id_fk": { + "name": "chat_memory_commands_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_memory_commands", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_memory_commands_conversation_id_command_id_pk": { + "name": "chat_memory_commands_conversation_id_command_id_pk", + "columns": ["conversation_id", "command_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_memory_preferences": { + "name": "chat_memory_preferences", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_memory_preferences_conversation_id_chat_conversations_id_fk": { + "name": "chat_memory_preferences_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_memory_preferences", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_plugin_bindings": { + "name": "chat_plugin_bindings", + "schema": "", + "columns": { + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "requirement_key": { + "name": "requirement_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_plugin_bindings_installation_id_version_chat_plugin_versions_installation_id_version_fk": { + "name": "chat_plugin_bindings_installation_id_version_chat_plugin_versions_installation_id_version_fk", + "tableFrom": "chat_plugin_bindings", + "tableTo": "chat_plugin_versions", + "columnsFrom": ["installation_id", "version"], + "columnsTo": ["installation_id", "version"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_plugin_bindings_installation_id_version_requirement_key_pk": { + "name": "chat_plugin_bindings_installation_id_version_requirement_key_pk", + "columns": ["installation_id", "version", "requirement_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_plugin_commands": { + "name": "chat_plugin_commands", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "command_id": { + "name": "command_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt": { + "name": "receipt", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_plugin_commands_workspace_id_chat_workspaces_id_fk": { + "name": "chat_plugin_commands_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_plugin_commands", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_plugin_commands_user_id_users_id_fk": { + "name": "chat_plugin_commands_user_id_users_id_fk", + "tableFrom": "chat_plugin_commands", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_plugin_commands_workspace_id_user_id_command_id_pk": { + "name": "chat_plugin_commands_workspace_id_user_id_command_id_pk", + "columns": ["workspace_id", "user_id", "command_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_plugin_files": { + "name": "chat_plugin_files", + "schema": "", + "columns": { + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_plugin_files_installation_id_version_chat_plugin_versions_installation_id_version_fk": { + "name": "chat_plugin_files_installation_id_version_chat_plugin_versions_installation_id_version_fk", + "tableFrom": "chat_plugin_files", + "tableTo": "chat_plugin_versions", + "columnsFrom": ["installation_id", "version"], + "columnsTo": ["installation_id", "version"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_plugin_files_installation_id_version_path_pk": { + "name": "chat_plugin_files_installation_id_version_path_pk", + "columns": ["installation_id", "version", "path"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_plugin_installations": { + "name": "chat_plugin_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "current_version": { + "name": "current_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "removed": { + "name": "removed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_plugin_owner_list": { + "name": "chat_plugin_owner_list", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "removed", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_plugin_installations_workspace_id_chat_workspaces_id_fk": { + "name": "chat_plugin_installations_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_plugin_installations", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_plugin_installations_user_id_users_id_fk": { + "name": "chat_plugin_installations_user_id_users_id_fk", + "tableFrom": "chat_plugin_installations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_plugin_version_positive": { + "name": "chat_plugin_version_positive", + "value": "\"chat_plugin_installations\".\"current_version\">0" + }, + "chat_plugin_revision_positive": { + "name": "chat_plugin_revision_positive", + "value": "\"chat_plugin_installations\".\"revision\">0" + } + }, + "isRLSEnabled": false + }, + "public.chat_plugin_skill_identities": { + "name": "chat_plugin_skill_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_plugin_skill_identities_installation_id_chat_plugin_installations_id_fk": { + "name": "chat_plugin_skill_identities_installation_id_chat_plugin_installations_id_fk", + "tableFrom": "chat_plugin_skill_identities", + "tableTo": "chat_plugin_installations", + "columnsFrom": ["installation_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_plugin_skill_path": { + "name": "chat_plugin_skill_path", + "nullsNotDistinct": false, + "columns": ["installation_id", "path"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_plugin_skill_versions": { + "name": "chat_plugin_skill_versions", + "schema": "", + "columns": { + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "document": { + "name": "document", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_plugin_skill_versions_skill_id_chat_plugin_skill_identities_id_fk": { + "name": "chat_plugin_skill_versions_skill_id_chat_plugin_skill_identities_id_fk", + "tableFrom": "chat_plugin_skill_versions", + "tableTo": "chat_plugin_skill_identities", + "columnsFrom": ["skill_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_plugin_skill_versions_skill_id_version_pk": { + "name": "chat_plugin_skill_versions_skill_id_version_pk", + "columns": ["skill_id", "version"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_plugin_versions": { + "name": "chat_plugin_versions", + "schema": "", + "columns": { + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "preview": { + "name": "preview", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_plugin_versions_installation_id_chat_plugin_installations_id_fk": { + "name": "chat_plugin_versions_installation_id_chat_plugin_installations_id_fk", + "tableFrom": "chat_plugin_versions", + "tableTo": "chat_plugin_installations", + "columnsFrom": ["installation_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_plugin_versions_installation_id_version_pk": { + "name": "chat_plugin_versions_installation_id_version_pk", + "columns": ["installation_id", "version"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_plugin_saved_version_positive": { + "name": "chat_plugin_saved_version_positive", + "value": "\"chat_plugin_versions\".\"version\">0" + } + }, + "isRLSEnabled": false + }, + "public.chat_recipes": { + "name": "chat_recipes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_recipes_workspace_id_chat_workspaces_id_fk": { + "name": "chat_recipes_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_recipes", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_run_usage_receipts": { + "name": "chat_run_usage_receipts", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled": { + "name": "scheduled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "attempt_id": { + "name": "attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_run_usage_receipts_conversation_id_chat_conversations_id_fk": { + "name": "chat_run_usage_receipts_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_run_usage_receipts", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_run_usage_receipts_workspace_id_chat_workspaces_id_fk": { + "name": "chat_run_usage_receipts_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_run_usage_receipts", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_run_usage_receipts_user_id_users_id_fk": { + "name": "chat_run_usage_receipts_user_id_users_id_fk", + "tableFrom": "chat_run_usage_receipts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_run_usage_receipts_bot_id_chat_bots_id_fk": { + "name": "chat_run_usage_receipts_bot_id_chat_bots_id_fk", + "tableFrom": "chat_run_usage_receipts", + "tableTo": "chat_bots", + "columnsFrom": ["bot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_run_usage_receipts_conversation_id_run_id_pk": { + "name": "chat_run_usage_receipts_conversation_id_run_id_pk", + "columns": ["conversation_id", "run_id"] + } + }, + "uniqueConstraints": { + "chat_run_usage_receipts_attempt_id_unique": { + "name": "chat_run_usage_receipts_attempt_id_unique", + "nullsNotDistinct": false, + "columns": ["attempt_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_saved_file_imports": { + "name": "chat_saved_file_imports", + "schema": "", + "columns": { + "target_file_id": { + "name": "target_file_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_conversation_id": { + "name": "target_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_conversation_id": { + "name": "source_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_file_id": { + "name": "source_file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_file_import_target_idx": { + "name": "chat_file_import_target_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_saved_file_imports_workspace_id_chat_workspaces_id_fk": { + "name": "chat_saved_file_imports_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_saved_file_imports", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_saved_file_imports_user_id_users_id_fk": { + "name": "chat_saved_file_imports_user_id_users_id_fk", + "tableFrom": "chat_saved_file_imports", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_saved_file_imports_target_conversation_id_chat_conversations_id_fk": { + "name": "chat_saved_file_imports_target_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_saved_file_imports", + "tableTo": "chat_conversations", + "columnsFrom": ["target_conversation_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_file_import_size": { + "name": "chat_file_import_size", + "value": "\"chat_saved_file_imports\".\"size\">=0 AND \"chat_saved_file_imports\".\"size\"<=2097152" + }, + "chat_file_import_digest": { + "name": "chat_file_import_digest", + "value": "\"chat_saved_file_imports\".\"sha256\" ~ '^[0-9a-f]{64}$'" + }, + "chat_file_import_source": { + "name": "chat_file_import_source", + "value": "\"chat_saved_file_imports\".\"source\" IN ('upload','assistant')" + } + }, + "isRLSEnabled": false + }, + "public.chat_saved_files": { + "name": "chat_saved_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bot_id": { + "name": "bot_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "draft_id": { + "name": "draft_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_file_conversation_idx": { + "name": "chat_file_conversation_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_file_draft_idx": { + "name": "chat_file_draft_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "draft_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_saved_files_workspace_id_chat_workspaces_id_fk": { + "name": "chat_saved_files_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_saved_files", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_saved_files_user_id_users_id_fk": { + "name": "chat_saved_files_user_id_users_id_fk", + "tableFrom": "chat_saved_files", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_file_draft_fk": { + "name": "chat_file_draft_fk", + "tableFrom": "chat_saved_files", + "tableTo": "chat_file_drafts", + "columnsFrom": ["workspace_id", "user_id", "draft_id"], + "columnsTo": ["workspace_id", "user_id", "id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_file_conversation_fk": { + "name": "chat_file_conversation_fk", + "tableFrom": "chat_saved_files", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id", "bot_id", "user_id"], + "columnsTo": ["id", "bot_id", "user_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_file_workspace_fk": { + "name": "chat_file_workspace_fk", + "tableFrom": "chat_saved_files", + "tableTo": "chat_bots", + "columnsFrom": ["workspace_id", "bot_id"], + "columnsTo": ["workspace_id", "id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_file_scope": { + "name": "chat_file_scope", + "value": "(\"chat_saved_files\".\"draft_id\" IS NOT NULL AND \"chat_saved_files\".\"bot_id\" IS NULL AND \"chat_saved_files\".\"conversation_id\" IS NULL) OR (\"chat_saved_files\".\"draft_id\" IS NULL AND \"chat_saved_files\".\"bot_id\" IS NOT NULL AND \"chat_saved_files\".\"conversation_id\" IS NOT NULL)" + }, + "chat_file_size": { + "name": "chat_file_size", + "value": "\"chat_saved_files\".\"size\">=0 AND \"chat_saved_files\".\"size\"<=2097152" + }, + "chat_file_name": { + "name": "chat_file_name", + "value": "length(\"chat_saved_files\".\"name\") BETWEEN 1 AND 180" + }, + "chat_file_digest": { + "name": "chat_file_digest", + "value": "\"chat_saved_files\".\"sha256\" ~ '^[0-9a-f]{64}$'" + }, + "chat_file_source": { + "name": "chat_file_source", + "value": "\"chat_saved_files\".\"source\" IN ('upload','assistant')" + }, + "chat_file_state": { + "name": "chat_file_state", + "value": "\"chat_saved_files\".\"state\" IN ('pending','ready')" + } + }, + "isRLSEnabled": false + }, + "public.chat_scheduled_daily_usage": { + "name": "chat_scheduled_daily_usage", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "day": { + "name": "day", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "turns": { + "name": "turns", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "chat_scheduled_daily_usage_user_id_users_id_fk": { + "name": "chat_scheduled_daily_usage_user_id_users_id_fk", + "tableFrom": "chat_scheduled_daily_usage", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_scheduled_daily_usage_user_id_day_pk": { + "name": "chat_scheduled_daily_usage_user_id_day_pk", + "columns": ["user_id", "day"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_scheduled_turns_nonnegative": { + "name": "chat_scheduled_turns_nonnegative", + "value": "\"chat_scheduled_daily_usage\".\"turns\">=0" + } + }, + "isRLSEnabled": false + }, + "public.chat_skill_commands": { + "name": "chat_skill_commands", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "command_id": { + "name": "command_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "receipt": { + "name": "receipt", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_skill_commands_workspace_id_chat_workspaces_id_fk": { + "name": "chat_skill_commands_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_skill_commands", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_skill_commands_user_id_users_id_fk": { + "name": "chat_skill_commands_user_id_users_id_fk", + "tableFrom": "chat_skill_commands", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_skill_commands_workspace_id_user_id_command_id_pk": { + "name": "chat_skill_commands_workspace_id_user_id_command_id_pk", + "columns": ["workspace_id", "user_id", "command_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_skill_versions": { + "name": "chat_skill_versions", + "schema": "", + "columns": { + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "document": { + "name": "document", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_skill_versions_skill_id_chat_skills_id_fk": { + "name": "chat_skill_versions_skill_id_chat_skills_id_fk", + "tableFrom": "chat_skill_versions", + "tableTo": "chat_skills", + "columnsFrom": ["skill_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_skill_versions_skill_id_version_pk": { + "name": "chat_skill_versions_skill_id_version_pk", + "columns": ["skill_id", "version"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_skill_saved_version": { + "name": "chat_skill_saved_version", + "value": "\"chat_skill_versions\".\"version\">0 AND \"chat_skill_versions\".\"version\"<=9007199254740991" + } + }, + "isRLSEnabled": false + }, + "public.chat_skills": { + "name": "chat_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "chat_skill_owner_list": { + "name": "chat_skill_owner_list", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_skills_workspace_id_chat_workspaces_id_fk": { + "name": "chat_skills_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_skills", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_skills_user_id_users_id_fk": { + "name": "chat_skills_user_id_users_id_fk", + "tableFrom": "chat_skills", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_skill_version": { + "name": "chat_skill_version", + "value": "\"chat_skills\".\"version\">0 AND \"chat_skills\".\"version\"<=9007199254740991" + }, + "chat_skill_revision": { + "name": "chat_skill_revision", + "value": "\"chat_skills\".\"revision\">0 AND \"chat_skills\".\"revision\"<=9007199254740991" + } + }, + "isRLSEnabled": false + }, + "public.chat_thread_requests": { + "name": "chat_thread_requests", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_digest": { + "name": "request_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_conversation_id": { + "name": "parent_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_message_id": { + "name": "source_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_thread_requests_workspace_id_chat_workspaces_id_fk": { + "name": "chat_thread_requests_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_thread_requests", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_thread_requests_user_id_users_id_fk": { + "name": "chat_thread_requests_user_id_users_id_fk", + "tableFrom": "chat_thread_requests", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_thread_requests_conversation_id_chat_conversations_id_fk": { + "name": "chat_thread_requests_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_thread_requests", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_thread_requests_workspace_id_user_id_idempotency_key_pk": { + "name": "chat_thread_requests_workspace_id_user_id_idempotency_key_pk", + "columns": ["workspace_id", "user_id", "idempotency_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_thread_schedule_suspensions": { + "name": "chat_thread_schedule_suspensions", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "chat_thread_schedule_suspensions_conversation_id_chat_conversation_threads_conversation_id_fk": { + "name": "chat_thread_schedule_suspensions_conversation_id_chat_conversation_threads_conversation_id_fk", + "tableFrom": "chat_thread_schedule_suspensions", + "tableTo": "chat_conversation_threads", + "columnsFrom": ["conversation_id"], + "columnsTo": ["conversation_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "chat_thread_generation_safe": { + "name": "chat_thread_generation_safe", + "value": "\"chat_thread_schedule_suspensions\".\"generation\" BETWEEN 0 AND 9007199254740991" + } + }, + "isRLSEnabled": false + }, + "public.chat_waitlist": { + "name": "chat_waitlist", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_waitlist_user_id_users_id_fk": { + "name": "chat_waitlist_user_id_users_id_fk", + "tableFrom": "chat_waitlist", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_workflow_children": { + "name": "chat_workflow_children", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_conversation_id": { + "name": "owner_conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_run_id": { + "name": "workflow_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "step_id": { + "name": "step_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "admission_json": { + "name": "admission_json", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_workflow_children_conversation_id_chat_conversations_id_fk": { + "name": "chat_workflow_children_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_workflow_children", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_workflow_children_owner_conversation_id_chat_conversations_id_fk": { + "name": "chat_workflow_children_owner_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_workflow_children", + "tableTo": "chat_conversations", + "columnsFrom": ["owner_conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_workflow_children_owner_conversation_id_workflow_run_id_step_id_unique": { + "name": "chat_workflow_children_owner_conversation_id_workflow_run_id_step_id_unique", + "nullsNotDistinct": false, + "columns": ["owner_conversation_id", "workflow_run_id", "step_id"] + } + }, + "policies": {}, + "checkConstraints": { + "chat_workflow_child_admission_json": { + "name": "chat_workflow_child_admission_json", + "value": "jsonb_typeof(\"chat_workflow_children\".\"admission_json\"::jsonb)='object'" + } + }, + "isRLSEnabled": false + }, + "public.chat_workflow_revisions": { + "name": "chat_workflow_revisions", + "schema": "", + "columns": { + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "command_id": { + "name": "command_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition_json": { + "name": "definition_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "chat_workflow_revisions_conversation_id_chat_conversations_id_fk": { + "name": "chat_workflow_revisions_conversation_id_chat_conversations_id_fk", + "tableFrom": "chat_workflow_revisions", + "tableTo": "chat_conversations", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_workflow_revisions_workspace_id_chat_workspaces_id_fk": { + "name": "chat_workflow_revisions_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_workflow_revisions", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_workflow_revisions_user_id_users_id_fk": { + "name": "chat_workflow_revisions_user_id_users_id_fk", + "tableFrom": "chat_workflow_revisions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_workflow_revisions_conversation_id_workflow_id_revision_pk": { + "name": "chat_workflow_revisions_conversation_id_workflow_id_revision_pk", + "columns": ["conversation_id", "workflow_id", "revision"] + } + }, + "uniqueConstraints": { + "chat_workflow_revisions_conversation_id_command_id_unique": { + "name": "chat_workflow_revisions_conversation_id_command_id_unique", + "nullsNotDistinct": false, + "columns": ["conversation_id", "command_id"] + } + }, + "policies": {}, + "checkConstraints": { + "chat_workflow_revision_positive": { + "name": "chat_workflow_revision_positive", + "value": "\"chat_workflow_revisions\".\"revision\">0" + } + }, + "isRLSEnabled": false + }, + "public.chat_workspace_sync_clock": { + "name": "chat_workspace_sync_clock", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "published_revision": { + "name": "published_revision", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "chat_workspace_sync_clock_workspace_id_chat_workspaces_id_fk": { + "name": "chat_workspace_sync_clock_workspace_id_chat_workspaces_id_fk", + "tableFrom": "chat_workspace_sync_clock", + "tableTo": "chat_workspaces", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_workspace_sync_members": { + "name": "chat_workspace_sync_members", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_workspace_sync_members_workspace_id_user_id_chat_memberships_workspace_id_user_id_fk": { + "name": "chat_workspace_sync_members_workspace_id_user_id_chat_memberships_workspace_id_user_id_fk", + "tableFrom": "chat_workspace_sync_members", + "tableTo": "chat_memberships", + "columnsFrom": ["workspace_id", "user_id"], + "columnsTo": ["workspace_id", "user_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_workspace_sync_members_workspace_id_user_id_pk": { + "name": "chat_workspace_sync_members_workspace_id_user_id_pk", + "columns": ["workspace_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_workspaces": { + "name": "chat_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policy": { + "name": "policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_workspaces_owner_idx": { + "name": "chat_workspaces_owner_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workspaces_owner_id_users_id_fk": { + "name": "chat_workspaces_owner_id_users_id_fk", + "tableFrom": "chat_workspaces", + "tableTo": "users", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.doc_feedback": { + "name": "doc_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "doc_feedback_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "page_path": { + "name": "page_path", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "library_version": { + "name": "library_version", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "block_selector": { + "name": "block_selector", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_content_hash": { + "name": "block_content_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "block_markdown": { + "name": "block_markdown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "doc_feedback_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "is_detached": { + "name": "is_detached", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_collapsed": { + "name": "is_collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "moderated_by": { + "name": "moderated_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "moderated_at": { + "name": "moderated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "moderation_note": { + "name": "moderation_note", + "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": { + "doc_feedback_user_id_idx": { + "name": "doc_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_feedback_status_idx": { + "name": "doc_feedback_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_feedback_library_idx": { + "name": "doc_feedback_library_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_feedback_page_path_idx": { + "name": "doc_feedback_page_path_idx", + "columns": [ + { + "expression": "page_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_feedback_created_at_idx": { + "name": "doc_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_feedback_is_detached_idx": { + "name": "doc_feedback_is_detached_idx", + "columns": [ + { + "expression": "is_detached", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_feedback_moderated_by_idx": { + "name": "doc_feedback_moderated_by_idx", + "columns": [ + { + "expression": "moderated_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "doc_feedback_user_id_users_id_fk": { + "name": "doc_feedback_user_id_users_id_fk", + "tableFrom": "doc_feedback", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "doc_feedback_moderated_by_users_id_fk": { + "name": "doc_feedback_moderated_by_users_id_fk", + "tableFrom": "doc_feedback", + "tableTo": "users", + "columnsFrom": ["moderated_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_artifact_cache": { + "name": "docs_artifact_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "repo": { + "name": "repo", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "git_ref": { + "name": "git_ref", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "docs_root": { + "name": "docs_root", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "artifact_type": { + "name": "artifact_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "artifact_key": { + "name": "artifact_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stale_at": { + "name": "stale_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": { + "docs_artifact_cache_repo_ref_docs_root_idx": { + "name": "docs_artifact_cache_repo_ref_docs_root_idx", + "columns": [ + { + "expression": "repo", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "docs_root", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_artifact_cache_stale_at_idx": { + "name": "docs_artifact_cache_stale_at_idx", + "columns": [ + { + "expression": "stale_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_artifact_cache_unique": { + "name": "docs_artifact_cache_unique", + "columns": [ + { + "expression": "repo", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "docs_root", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "artifact_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "artifact_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_content_cache": { + "name": "github_content_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "repo": { + "name": "repo", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "git_ref": { + "name": "git_ref", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "content_kind": { + "name": "content_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": true + }, + "is_present": { + "name": "is_present", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "text_content": { + "name": "text_content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "json_content": { + "name": "json_content", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "stale_at": { + "name": "stale_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": { + "github_content_cache_repo_ref_idx": { + "name": "github_content_cache_repo_ref_idx", + "columns": [ + { + "expression": "repo", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_content_cache_stale_at_idx": { + "name": "github_content_cache_stale_at_idx", + "columns": [ + { + "expression": "stale_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_content_cache_unique": { + "name": "github_content_cache_unique", + "columns": [ + { + "expression": "repo", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "content_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_stats_cache": { + "name": "github_stats_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cache_key": { + "name": "cache_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "stats": { + "name": "stats", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "previous_stats": { + "name": "previous_stats", + "type": "jsonb", + "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": { + "github_stats_cache_key_idx": { + "name": "github_stats_cache_key_idx", + "columns": [ + { + "expression": "cache_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_stats_cache_expires_at_idx": { + "name": "github_stats_cache_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_stats_cache_cache_key_unique": { + "name": "github_stats_cache_cache_key_unique", + "nullsNotDistinct": false, + "columns": ["cache_key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.intent_package_versions": { + "name": "intent_package_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "intent_package_versions_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "package_name": { + "name": "package_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "skill_count": { + "name": "skill_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "tarball_url": { + "name": "tarball_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "sync_status": { + "name": "sync_status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "synced_at": { + "name": "synced_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()" + } + }, + "indexes": { + "intent_package_versions_package_name_idx": { + "name": "intent_package_versions_package_name_idx", + "columns": [ + { + "expression": "package_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "intent_package_versions_sync_status_idx": { + "name": "intent_package_versions_sync_status_idx", + "columns": [ + { + "expression": "sync_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "intent_package_versions_package_version_unique": { + "name": "intent_package_versions_package_version_unique", + "columns": [ + { + "expression": "package_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "intent_package_versions_package_name_intent_packages_name_fk": { + "name": "intent_package_versions_package_name_intent_packages_name_fk", + "tableFrom": "intent_package_versions", + "tableTo": "intent_packages", + "columnsFrom": ["package_name"], + "columnsTo": ["name"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.intent_packages": { + "name": "intent_packages", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.intent_skill_content": { + "name": "intent_skill_content", + "schema": "", + "columns": { + "content_hash": { + "name": "content_hash", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.intent_skills": { + "name": "intent_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "intent_skills_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "package_version_id": { + "name": "package_version_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "framework": { + "name": "framework", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "requires": { + "name": "requires", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "skill_path": { + "name": "skill_path", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "line_count": { + "name": "line_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "intent_skills_package_version_id_idx": { + "name": "intent_skills_package_version_id_idx", + "columns": [ + { + "expression": "package_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "intent_skills_name_idx": { + "name": "intent_skills_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "intent_skills_framework_idx": { + "name": "intent_skills_framework_idx", + "columns": [ + { + "expression": "framework", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "intent_skills_type_idx": { + "name": "intent_skills_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "intent_skills_package_version_skill_unique": { + "name": "intent_skills_package_version_skill_unique", + "columns": [ + { + "expression": "package_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "intent_skills_package_version_id_intent_package_versions_id_fk": { + "name": "intent_skills_package_version_id_intent_package_versions_id_fk", + "tableFrom": "intent_skills", + "tableTo": "intent_package_versions", + "columnsFrom": ["package_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "intent_skills_content_hash_intent_skill_content_content_hash_fk": { + "name": "intent_skills_content_hash_intent_skill_content_content_hash_fk", + "tableFrom": "intent_skills", + "tableTo": "intent_skill_content", + "columnsFrom": ["content_hash"], + "columnsTo": ["content_hash"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.live_sessions": { + "name": "live_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "company": { + "name": "company", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lookup_status": { + "name": "lookup_status", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "live_sessions_created_at_idx": { + "name": "live_sessions_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.live_tabs": { + "name": "live_tabs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "page": { + "name": "page", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visible": { + "name": "visible", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "last_seen": { + "name": "last_seen", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "left_at": { + "name": "left_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "activity": { + "name": "activity", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "live_tabs_last_seen_idx": { + "name": "live_tabs_last_seen_idx", + "columns": [ + { + "expression": "last_seen", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "live_tabs_session_idx": { + "name": "live_tabs_session_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "live_tabs_session_id_live_sessions_id_fk": { + "name": "live_tabs_session_id_live_sessions_id_fk", + "tableFrom": "live_tabs", + "tableTo": "live_sessions", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.login_history": { + "name": "login_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "oauth_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_new_user": { + "name": "is_new_user", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "login_history_user_id_idx": { + "name": "login_history_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_history_created_at_idx": { + "name": "login_history_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_history_provider_idx": { + "name": "login_history_provider_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "login_history_user_id_users_id_fk": { + "name": "login_history_user_id_users_id_fk", + "tableFrom": "login_history", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_api_keys": { + "name": "mcp_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key_hash": { + "name": "key_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "key_prefix": { + "name": "key_prefix", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "rate_limit_per_minute": { + "name": "rate_limit_per_minute", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 200 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_used_at": { + "name": "last_used_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": { + "mcp_api_keys_key_hash_idx": { + "name": "mcp_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_api_keys_user_id_idx": { + "name": "mcp_api_keys_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_api_keys_is_active_idx": { + "name": "mcp_api_keys_is_active_idx", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_api_keys_user_id_users_id_fk": { + "name": "mcp_api_keys_user_id_users_id_fk", + "tableFrom": "mcp_api_keys", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mcp_api_keys_key_hash_unique": { + "name": "mcp_api_keys_key_hash_unique", + "nullsNotDistinct": false, + "columns": ["key_hash"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_rate_limits": { + "name": "mcp_rate_limits", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "identifier": { + "name": "identifier", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "identifier_type": { + "name": "identifier_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + } + }, + "indexes": { + "mcp_rate_limits_identifier_window_unique": { + "name": "mcp_rate_limits_identifier_window_unique", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_rate_limits_identifier_idx": { + "name": "mcp_rate_limits_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_rate_limits_window_start_idx": { + "name": "mcp_rate_limits_window_start_idx", + "columns": [ + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.npm_download_chunks": { + "name": "npm_download_chunks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "package_name": { + "name": "package_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "date_from": { + "name": "date_from", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "date_to": { + "name": "date_to", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "bin_size": { + "name": "bin_size", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'daily'" + }, + "total_downloads": { + "name": "total_downloads", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "daily_data": { + "name": "daily_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "is_immutable": { + "name": "is_immutable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "npm_download_chunks_package_date_bin_unique": { + "name": "npm_download_chunks_package_date_bin_unique", + "columns": [ + { + "expression": "package_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_from", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_to", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bin_size", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "npm_download_chunks_package_name_idx": { + "name": "npm_download_chunks_package_name_idx", + "columns": [ + { + "expression": "package_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "npm_download_chunks_date_from_idx": { + "name": "npm_download_chunks_date_from_idx", + "columns": [ + { + "expression": "date_from", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "npm_download_chunks_date_to_idx": { + "name": "npm_download_chunks_date_to_idx", + "columns": [ + { + "expression": "date_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "npm_download_chunks_expires_at_idx": { + "name": "npm_download_chunks_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "npm_download_chunks_is_immutable_idx": { + "name": "npm_download_chunks_is_immutable_idx", + "columns": [ + { + "expression": "is_immutable", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_mcp_access_tokens": { + "name": "oauth_mcp_access_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'api'" + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oauth_access_grant_idx": { + "name": "oauth_access_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_mcp_access_tokens_hash_idx": { + "name": "oauth_mcp_access_tokens_hash_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_mcp_access_tokens_user_idx": { + "name": "oauth_mcp_access_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_mcp_access_tokens_expires_idx": { + "name": "oauth_mcp_access_tokens_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_mcp_access_tokens_user_id_users_id_fk": { + "name": "oauth_mcp_access_tokens_user_id_users_id_fk", + "tableFrom": "oauth_mcp_access_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_mcp_access_tokens_token_hash_unique": { + "name": "oauth_mcp_access_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": ["token_hash"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_accounts": { + "name": "oauth_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "oauth_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_scope": { + "name": "token_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oauth_accounts_user_id_idx": { + "name": "oauth_accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_accounts_provider_account_unique": { + "name": "oauth_accounts_provider_account_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_accounts_provider_account_idx": { + "name": "oauth_accounts_provider_account_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_accounts_user_id_users_id_fk": { + "name": "oauth_accounts_user_id_users_id_fk", + "tableFrom": "oauth_accounts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_mcp_authorization_codes": { + "name": "oauth_mcp_authorization_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "code_hash": { + "name": "code_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code_challenge": { + "name": "code_challenge", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true, + "default": "'S256'" + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'api'" + }, + "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": { + "oauth_mcp_auth_codes_hash_idx": { + "name": "oauth_mcp_auth_codes_hash_idx", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_mcp_auth_codes_expires_idx": { + "name": "oauth_mcp_auth_codes_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_mcp_authorization_codes_user_id_users_id_fk": { + "name": "oauth_mcp_authorization_codes_user_id_users_id_fk", + "tableFrom": "oauth_mcp_authorization_codes", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_mcp_authorization_codes_code_hash_unique": { + "name": "oauth_mcp_authorization_codes_code_hash_unique", + "nullsNotDistinct": false, + "columns": ["code_hash"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_mcp_refresh_tokens": { + "name": "oauth_mcp_refresh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'api'" + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "access_token_id": { + "name": "access_token_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": { + "oauth_refresh_grant_idx": { + "name": "oauth_refresh_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_mcp_refresh_tokens_hash_idx": { + "name": "oauth_mcp_refresh_tokens_hash_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_mcp_refresh_tokens_user_idx": { + "name": "oauth_mcp_refresh_tokens_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_mcp_refresh_tokens_user_id_users_id_fk": { + "name": "oauth_mcp_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oauth_mcp_refresh_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_mcp_refresh_tokens_access_token_id_oauth_mcp_access_tokens_id_fk": { + "name": "oauth_mcp_refresh_tokens_access_token_id_oauth_mcp_access_tokens_id_fk", + "tableFrom": "oauth_mcp_refresh_tokens", + "tableTo": "oauth_mcp_access_tokens", + "columnsFrom": ["access_token_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_mcp_refresh_tokens_token_hash_unique": { + "name": "oauth_mcp_refresh_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": ["token_hash"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oss_stats_cache": { + "name": "oss_stats_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scope_type": { + "name": "scope_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "github_star_count": { + "name": "github_star_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "github_contributor_count": { + "name": "github_contributor_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "github_dependent_count": { + "name": "github_dependent_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "github_fork_count": { + "name": "github_fork_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "github_repository_count": { + "name": "github_repository_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "github_delta_star_count": { + "name": "github_delta_star_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "github_delta_contributor_count": { + "name": "github_delta_contributor_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "github_delta_dependent_count": { + "name": "github_delta_dependent_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "github_delta_fork_count": { + "name": "github_delta_fork_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "github_updated_at": { + "name": "github_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "npm_total_downloads": { + "name": "npm_total_downloads", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "npm_rate_per_day": { + "name": "npm_rate_per_day", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "npm_package_count": { + "name": "npm_package_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "npm_updated_at": { + "name": "npm_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "time_delta_ms": { + "name": "time_delta_ms", + "type": "bigint", + "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": { + "oss_stats_cache_scope_unique": { + "name": "oss_stats_cache_scope_unique", + "columns": [ + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oss_stats_cache_scope_type_idx": { + "name": "oss_stats_cache_scope_type_idx", + "columns": [ + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oss_stats_cache_scope_key_idx": { + "name": "oss_stats_cache_scope_key_idx", + "columns": [ + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.role_assignments": { + "name": "role_assignments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "role_assignments_user_id_idx": { + "name": "role_assignments_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "role_assignments_role_id_idx": { + "name": "role_assignments_role_id_idx", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "role_assignments_user_role_unique": { + "name": "role_assignments_user_role_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "role_assignments_user_id_users_id_fk": { + "name": "role_assignments_user_id_users_id_fk", + "tableFrom": "role_assignments", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "role_assignments_role_id_roles_id_fk": { + "name": "role_assignments_role_id_roles_id_fk", + "tableFrom": "role_assignments", + "tableTo": "roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "capability[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "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": { + "roles_name_idx": { + "name": "roles_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "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": true, + "default": "now()" + }, + "ip_address": { + "name": "ip_address", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_token_idx": { + "name": "sessions_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_expires_at_idx": { + "name": "sessions_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.showcase_votes": { + "name": "showcase_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "showcase_id": { + "name": "showcase_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "integer", + "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": { + "showcase_votes_showcase_id_idx": { + "name": "showcase_votes_showcase_id_idx", + "columns": [ + { + "expression": "showcase_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "showcase_votes_user_id_idx": { + "name": "showcase_votes_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "showcase_votes_showcase_id_showcases_id_fk": { + "name": "showcase_votes_showcase_id_showcases_id_fk", + "tableFrom": "showcase_votes", + "tableTo": "showcases", + "columnsFrom": ["showcase_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "showcase_votes_user_id_users_id_fk": { + "name": "showcase_votes_user_id_users_id_fk", + "tableFrom": "showcase_votes", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "showcase_votes_showcase_id_user_id_unique": { + "name": "showcase_votes_showcase_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["showcase_id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.showcases": { + "name": "showcases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tagline": { + "name": "tagline", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "screenshot_url": { + "name": "screenshot_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "libraries": { + "name": "libraries", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "use_cases": { + "name": "use_cases", + "type": "showcase_use_case[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "is_featured": { + "name": "is_featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "showcase_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "placement": { + "name": "placement", + "type": "showcase_placement", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "review_reason": { + "name": "review_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "moderated_by": { + "name": "moderated_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "moderated_at": { + "name": "moderated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "moderation_note": { + "name": "moderation_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tranco_rank": { + "name": "tranco_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tranco_rank_updated_at": { + "name": "tranco_rank_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "vote_score": { + "name": "vote_score", + "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": { + "showcases_user_id_idx": { + "name": "showcases_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "showcases_status_idx": { + "name": "showcases_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "showcases_status_placement_idx": { + "name": "showcases_status_placement_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "placement", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "showcases_is_featured_idx": { + "name": "showcases_is_featured_idx", + "columns": [ + { + "expression": "is_featured", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "showcases_created_at_idx": { + "name": "showcases_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "showcases_moderated_by_idx": { + "name": "showcases_moderated_by_idx", + "columns": [ + { + "expression": "moderated_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "showcases_tranco_rank_idx": { + "name": "showcases_tranco_rank_idx", + "columns": [ + { + "expression": "tranco_rank", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "showcases_vote_score_idx": { + "name": "showcases_vote_score_idx", + "columns": [ + { + "expression": "vote_score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "showcases_user_id_users_id_fk": { + "name": "showcases_user_id_users_id_fk", + "tableFrom": "showcases", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "showcases_moderated_by_users_id_fk": { + "name": "showcases_moderated_by_users_id_fk", + "tableFrom": "showcases", + "tableTo": "users", + "columnsFrom": ["moderated_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_activity": { + "name": "user_activity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_activity_user_date_unique": { + "name": "user_activity_user_date_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_activity_user_id_idx": { + "name": "user_activity_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_activity_date_idx": { + "name": "user_activity_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_activity_user_id_users_id_fk": { + "name": "user_activity_user_id_users_id_fk", + "tableFrom": "user_activity", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "display_username": { + "name": "display_username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_image": { + "name": "oauth_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "capability[]", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "ads_disabled": { + "name": "ads_disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "interested_in_hiding_ads": { + "name": "interested_in_hiding_ads", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "last_used_framework": { + "name": "last_used_framework", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "signup_sources": { + "name": "signup_sources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "session_version": { + "name": "session_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": { + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_created_at_idx": { + "name": "users_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.audit_action": { + "name": "audit_action", + "schema": "public", + "values": [ + "user.capabilities.update", + "user.adsDisabled.update", + "user.sessions.revoke", + "role.create", + "role.update", + "role.delete", + "role.assignment.create", + "role.assignment.delete", + "feedback.moderate", + "showcase.create", + "showcase.update", + "showcase.delete", + "showcase.moderate" + ] + }, + "public.builder_message_role": { + "name": "builder_message_role", + "schema": "public", + "values": ["user", "assistant"] + }, + "public.builder_project_event_type": { + "name": "builder_project_event_type", + "schema": "public", + "values": [ + "project.created", + "project.updated", + "project.deleted", + "thread.created", + "thread.updated", + "thread.archived", + "revision.created", + "message.created", + "message.updated", + "message.deleted", + "run.created", + "run.started", + "run.activity", + "run.interrupted", + "run.completed", + "run.failed", + "run.cancelled" + ] + }, + "public.builder_run_status": { + "name": "builder_run_status", + "schema": "public", + "values": [ + "pending", + "running", + "interrupted", + "completed", + "failed", + "cancelled" + ] + }, + "public.capability": { + "name": "capability", + "schema": "public", + "values": [ + "admin", + "api-keys", + "disableAds", + "builder", + "mcp", + "moderate-feedback", + "moderate-showcases" + ] + }, + "public.doc_feedback_status": { + "name": "doc_feedback_status", + "schema": "public", + "values": ["pending", "approved", "denied"] + }, + "public.doc_feedback_type": { + "name": "doc_feedback_type", + "schema": "public", + "values": ["note", "improvement"] + }, + "public.oauth_provider": { + "name": "oauth_provider", + "schema": "public", + "values": ["github", "google"] + }, + "public.showcase_placement": { + "name": "showcase_placement", + "schema": "public", + "values": ["showcase", "community", "private"] + }, + "public.showcase_status": { + "name": "showcase_status", + "schema": "public", + "values": ["pending", "approved", "denied"] + }, + "public.showcase_use_case": { + "name": "showcase_use_case", + "schema": "public", + "values": [ + "blog", + "e-commerce", + "saas", + "dashboard", + "documentation", + "portfolio", + "social", + "developer-tool", + "marketing", + "media", + "open-source" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/drizzle/migrations/meta/_journal.json b/drizzle/migrations/meta/_journal.json index 46d6ebc53..a7bfec643 100644 --- a/drizzle/migrations/meta/_journal.json +++ b/drizzle/migrations/meta/_journal.json @@ -302,6 +302,13 @@ "when": 1793217188347, "tag": "0042_live_visitors", "breakpoints": true + }, + { + "idx": 43, + "version": "7", + "when": 1793316169908, + "tag": "0043_shared_oauth", + "breakpoints": true } ] } diff --git a/harness-tests/core/oauth-session-hint.test.ts b/harness-tests/core/oauth-session-hint.test.ts new file mode 100644 index 000000000..5d92d37d8 --- /dev/null +++ b/harness-tests/core/oauth-session-hint.test.ts @@ -0,0 +1,76 @@ +import { beforeEach, expect, it, vi } from 'vitest' +const auth = vi.hoisted(() => ({ user: vi.fn() })) +vi.mock('~/auth/index.server', () => ({ + getAuthService: () => ({ getCurrentUser: auth.user }), +})) +import { handleSessionHint } from '~/auth/oauth-session-hint.server' + +const request = (origin = 'https://source.tanstack.com', method = 'GET') => + new Request('https://tanstack.com/oauth/session-hint', { + method, + headers: { origin, cookie: 'session=opaque-cookie' }, + }) +beforeEach(() => { + auth.user.mockReset() + auth.user.mockResolvedValue(null) +}) +it('shares only an account display hint with Source, without tokens, email or identifiers', async () => { + auth.user.mockResolvedValue({ + userId: 'private-id', + email: 'private@example.com', + name: 'Tanner', + image: 'https://avatars.githubusercontent.com/u/1', + oauthImage: null, + displayUsername: 'tanner', + }) + const response = await handleSessionHint(request()) + expect(await response.json()).toEqual({ + account: { + name: 'Tanner', + avatar: 'https://avatars.githubusercontent.com/u/1', + }, + }) + expect(response.headers.get('access-control-allow-origin')).toBe( + 'https://source.tanstack.com', + ) + expect(response.headers.get('access-control-allow-credentials')).toBe('true') + expect(response.headers.get('cache-control')).toContain('no-store') + expect(response.headers.get('vary')).toContain('Cookie') +}) +it('does not look up sessions or expose a hint to another origin or a direct visit', async () => { + for (const origin of [ + 'https://evil.test', + 'https://source.tanstack.com.evil.test', + 'null', + '', + ]) { + const response = await handleSessionHint(request(origin)) + expect(response.status).toBe(403) + expect(response.headers.get('access-control-allow-origin')).toBeNull() + } + expect(auth.user).not.toHaveBeenCalled() +}) +it('returns no hint when signed out, and handles preflights without inspecting a session', async () => { + expect(await (await handleSessionHint(request())).json()).toEqual({ + account: null, + }) + auth.user.mockClear() + expect( + (await handleSessionHint(request('https://source.tanstack.com', 'OPTIONS'))) + .status, + ).toBe(204) + expect(auth.user).not.toHaveBeenCalled() +}) +it('uses the account name fallback and refuses non-HTTPS avatars', async () => { + auth.user.mockResolvedValue({ + userId: 'id', + email: 'private@example.com', + name: null, + displayUsername: 'tanner', + image: 'javascript:alert(1)', + oauthImage: null, + }) + expect(await (await handleSessionHint(request())).json()).toEqual({ + account: { name: 'tanner', avatar: null }, + }) +}) diff --git a/harness-tests/core/shared-oauth-db.test.ts b/harness-tests/core/shared-oauth-db.test.ts new file mode 100644 index 000000000..8fd00e4da --- /dev/null +++ b/harness-tests/core/shared-oauth-db.test.ts @@ -0,0 +1,350 @@ +import { + afterAll, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest' +import { readFile } from 'node:fs/promises' +import postgres from 'postgres' +import { drizzle } from 'drizzle-orm/postgres-js' +import { eq } from 'drizzle-orm' +import * as tables from '~/db/schema' +import { SOURCE_RESOURCE, IDENTITY_RESOURCE } from '~/auth/oauth-resources' + +const fixture = vi.hoisted(() => ({ + url: process.env.OAUTH_TEST_DATABASE_URL, + close: async () => {}, +})) +vi.mock('~/db/client', async () => { + const { default: postgres } = await import('postgres') + const { drizzle } = await import('drizzle-orm/postgres-js') + if (!fixture.url) return { db: {} } + const url = new URL(fixture.url) + if (url.hostname !== '127.0.0.1' || url.pathname !== '/shared_oauth_test') + throw new Error( + 'OAuth tests require the disposable local shared_oauth_test database', + ) + const client = postgres(fixture.url, { max: 8 }) + fixture.close = async () => { + await client.end() + } + return { db: drizzle(client) } +}) +vi.mock('~/utils/env.functions', () => ({ + envFunctions: { + SESSION_SECRET: 'test-secret', + TANSTACK_SOURCE_INTROSPECTION_SECRET: 'resource-secret', + }, +})) +import { + createAuthorizationCode, + createRegisteredClientId, + exchangeAuthorizationCode, + refreshAccessToken, + introspectSourceToken, + validateOAuthToken, + revokeOAuthGrant, + revokeTokensForClient, + listConnectedApps, +} from '~/auth/oauthClient.server' +import { + handleIntrospection, + handleUserInfo, +} from '~/auth/oauth-endpoints.server' + +const verifier = 'a'.repeat(43) +const userId = '00000000-0000-4000-8000-000000000001' +const redirectUri = 'https://client.test/callback' + +describe.skipIf(!fixture.url)('OAuth grants in PostgreSQL', () => { + const sql = postgres(fixture.url ?? 'postgres://invalid', { max: 4 }) + const db = drizzle(sql) + let clientId: string + let challenge: string + beforeAll(async () => { + await sql.unsafe(`CREATE TABLE users (id uuid PRIMARY KEY, email text, name text, display_username text); + CREATE TABLE oauth_accounts (user_id uuid REFERENCES users(id), provider text, provider_account_id text); + CREATE TABLE oauth_mcp_authorization_codes (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), code_hash varchar(64) UNIQUE NOT NULL, user_id uuid REFERENCES users(id), client_id varchar(255) NOT NULL, redirect_uri text NOT NULL, code_challenge varchar(128) NOT NULL, code_challenge_method varchar(8) NOT NULL DEFAULT 'S256', scope text NOT NULL DEFAULT 'api', expires_at timestamptz NOT NULL, created_at timestamptz NOT NULL DEFAULT now()); + CREATE TABLE oauth_mcp_access_tokens (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), token_hash varchar(64) UNIQUE NOT NULL, user_id uuid REFERENCES users(id), client_id varchar(255) NOT NULL, scope text NOT NULL DEFAULT 'api', expires_at timestamptz NOT NULL, last_used_at timestamptz, created_at timestamptz NOT NULL DEFAULT now()); + CREATE TABLE oauth_mcp_refresh_tokens (id uuid PRIMARY KEY DEFAULT gen_random_uuid(), token_hash varchar(64) UNIQUE NOT NULL, user_id uuid REFERENCES users(id), client_id varchar(255) NOT NULL, access_token_id uuid REFERENCES oauth_mcp_access_tokens(id) ON DELETE SET NULL, expires_at timestamptz NOT NULL, created_at timestamptz NOT NULL DEFAULT now());`) + await sql.unsafe( + await readFile( + new URL( + '../../drizzle/migrations/0043_shared_oauth.sql', + import.meta.url, + ), + 'utf8', + ), + ) + clientId = await createRegisteredClientId({ + clientName: 'Test client', + redirectUris: [redirectUri], + }) + challenge = Buffer.from( + await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)), + ).toString('base64url') + }) + beforeEach(async () => { + await sql.unsafe( + 'TRUNCATE oauth_mcp_refresh_tokens, oauth_mcp_access_tokens, oauth_mcp_authorization_codes, oauth_accounts, users CASCADE', + ) + await sql`INSERT INTO users (id, email, name, display_username) VALUES (${userId}, 'person@example.com', 'Person', 'person')` + }) + afterAll(async () => { + await sql.unsafe( + 'DROP TABLE oauth_mcp_refresh_tokens, oauth_mcp_access_tokens, oauth_mcp_authorization_codes, oauth_accounts, users CASCADE', + ) + await sql.end() + await fixture.close() + }) + + async function issue(resource: string | undefined = SOURCE_RESOURCE) { + const code = await createAuthorizationCode({ + userId, + clientId, + redirectUri, + codeChallenge: challenge, + resource, + }) + const result = await exchangeAuthorizationCode({ + code, + codeVerifier: verifier, + redirectUri, + clientId, + resource, + }) + if (!result.success) throw new Error(result.error) + return { ...result, code } + } + + it('binds codes to the client, exact redirect and resource, and consumes them once', async () => { + for (const override of [ + { clientId: 'other' }, + { redirectUri: redirectUri + '/' }, + { resource: IDENTITY_RESOURCE }, + { codeVerifier: 'b'.repeat(43) }, + ]) { + const code = await createAuthorizationCode({ + userId, + clientId, + redirectUri, + codeChallenge: challenge, + resource: SOURCE_RESOURCE, + }) + const params = { + code, + codeVerifier: verifier, + redirectUri, + clientId, + resource: SOURCE_RESOURCE, + } + expect( + await exchangeAuthorizationCode({ ...params, ...override }), + ).toEqual({ success: false, error: 'invalid_grant' }) + expect(await exchangeAuthorizationCode(params)).toEqual({ + success: false, + error: 'invalid_grant', + }) + } + const issued = await issue() + expect(await introspectSourceToken(issued.accessToken)).toMatchObject({ + active: true, + sub: userId, + aud: SOURCE_RESOURCE, + scope: 'source', + }) + expect((await validateOAuthToken(issued.accessToken)).success).toBe(false) + expect( + await exchangeAuthorizationCode({ + code: issued.code, + codeVerifier: verifier, + redirectUri, + clientId, + resource: SOURCE_RESOURCE, + }), + ).toEqual({ success: false, error: 'invalid_grant' }) + }) + + it('rotates refresh tokens without changing audience, scope or absolute expiry, and revokes on replay', async () => { + const issued = await issue() + expect(await listConnectedApps(userId)).toMatchObject([ + { clientName: 'Test client', clientId }, + ]) + const binding = { clientId, resource: SOURCE_RESOURCE } + expect( + ( + await refreshAccessToken(issued.refreshToken, { + clientId, + resource: IDENTITY_RESOURCE, + }) + ).success, + ).toBe(false) + const rotated = await refreshAccessToken(issued.refreshToken, binding) + expect(rotated).toMatchObject({ success: true, scope: 'source' }) + if (!rotated.success || !rotated.refreshToken) + throw new Error('Expected rotating refresh token') + expect(rotated.refreshToken).not.toBe(issued.refreshToken) + expect(await introspectSourceToken(rotated.accessToken)).toMatchObject({ + active: true, + aud: SOURCE_RESOURCE, + }) + expect( + (await refreshAccessToken(issued.refreshToken, binding)).success, + ).toBe(false) + expect(await introspectSourceToken(rotated.accessToken)).toEqual({ + active: false, + }) + expect( + (await refreshAccessToken(rotated.refreshToken, binding)).success, + ).toBe(false) + }) + + it('serializes competing refreshes and revokes every token in the replayed grant', async () => { + const issued = await issue() + const results = await Promise.all([ + refreshAccessToken(issued.refreshToken, { + clientId, + resource: SOURCE_RESOURCE, + }), + refreshAccessToken(issued.refreshToken, { + clientId, + resource: SOURCE_RESOURCE, + }), + ]) + expect(results.filter((r) => r.success)).toHaveLength(1) + for (const result of results) + if (result.success) + expect(await introspectSourceToken(result.accessToken)).toEqual({ + active: false, + }) + expect(await introspectSourceToken(issued.accessToken)).toEqual({ + active: false, + }) + }) + + it('revokes connected apps and individual grants, including concurrent refreshes', async () => { + const issued = await issue() + await Promise.all([ + refreshAccessToken(issued.refreshToken, { + clientId, + resource: SOURCE_RESOURCE, + }), + revokeTokensForClient(userId, clientId), + ]) + expect(await sql`SELECT * FROM oauth_mcp_access_tokens`).toHaveLength(0) + expect(await sql`SELECT * FROM oauth_mcp_refresh_tokens`).toHaveLength(0) + const another = await issue() + await revokeOAuthGrant(another.refreshToken, clientId) + expect(await introspectSourceToken(another.accessToken)).toEqual({ + active: false, + }) + }) + + it('keeps legacy grants and refresh tokens valid', async () => { + const code = await createAuthorizationCode({ + userId, + clientId, + redirectUri, + codeChallenge: challenge, + }) + const issued = await exchangeAuthorizationCode({ + code, + codeVerifier: verifier, + redirectUri, + }) + if (!issued.success) throw new Error(issued.error) + expect((await validateOAuthToken(issued.accessToken)).success).toBe(true) + expect((await refreshAccessToken(issued.refreshToken)).success).toBe(true) + expect((await refreshAccessToken(issued.refreshToken)).success).toBe(true) + expect(await introspectSourceToken(issued.accessToken)).toEqual({ + active: false, + }) + }) + + it('revokes access tokens created by legacy-scope refresh', async () => { + const code = await createAuthorizationCode({ + userId, + clientId, + redirectUri, + codeChallenge: challenge, + }) + const issued = await exchangeAuthorizationCode({ + code, + codeVerifier: verifier, + redirectUri, + }) + if (!issued.success) throw new Error(issued.error) + const refreshed = await refreshAccessToken(issued.refreshToken) + if (!refreshed.success) throw new Error(refreshed.error) + await revokeOAuthGrant(issued.refreshToken, clientId) + expect((await validateOAuthToken(refreshed.accessToken)).success).toBe( + false, + ) + }) + + it('requires service authentication for introspection and isolates identity grants', async () => { + const issued = await issue() + const request = (authorization: string) => + new Request('https://tanstack.com/oauth/introspect', { + method: 'POST', + headers: { + authorization, + 'content-type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + token: issued.accessToken, + resource: SOURCE_RESOURCE, + }), + }) + expect((await handleIntrospection(request('Bearer wrong'))).status).toBe( + 401, + ) + expect( + await ( + await handleIntrospection(request('Bearer resource-secret')) + ).json(), + ).toMatchObject({ active: true, sub: userId }) + await sql`INSERT INTO oauth_accounts (user_id, provider, provider_account_id) VALUES (${userId}, 'github', '583231')` + expect(await introspectSourceToken(issued.accessToken)).toMatchObject({ + github_id: '583231', + }) + const identity = await issue(IDENTITY_RESOURCE) + expect( + await ( + await handleUserInfo( + new Request('https://tanstack.com/oauth/userinfo', { + headers: { authorization: `Bearer ${identity.accessToken}` }, + }), + ) + ).json(), + ).toMatchObject({ + sub: userId, + email: 'person@example.com', + github_id: '583231', + }) + expect( + ( + await handleUserInfo( + new Request('https://tanstack.com/oauth/userinfo', { + headers: { authorization: `Bearer ${issued.accessToken}` }, + }), + ) + ).status, + ).toBe(401) + await sql`INSERT INTO oauth_accounts (user_id, provider, provider_account_id) VALUES (${userId}, 'github', 'another-id')` + expect(await introspectSourceToken(issued.accessToken)).toMatchObject({ + active: true, + github_id: null, + }) + await db + .update(tables.oauthAccessTokens) + .set({ expiresAt: new Date(0) }) + .where(eq(tables.oauthAccessTokens.userId, userId)) + expect(await introspectSourceToken(issued.accessToken)).toEqual({ + active: false, + }) + }) +}) diff --git a/harness-tests/core/shared-oauth.test.ts b/harness-tests/core/shared-oauth.test.ts new file mode 100644 index 000000000..967f27cd6 --- /dev/null +++ b/harness-tests/core/shared-oauth.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it, vi } from 'vitest' +vi.mock('~/db/client', () => ({ db: {} })) +vi.mock('~/utils/env.functions', () => ({ + envFunctions: { SESSION_SECRET: 'test-secret' }, +})) +import { + createRegisteredClientId, + validateOAuthClientRedirectUri, + validateRedirectUri, + verifyCodeChallenge, +} from '~/auth/oauthClient.server' +import { + authorizationGrant, + SOURCE_RESOURCE, + IDENTITY_RESOURCE, +} from '~/auth/oauth-resources' + +describe('shared TanStack authorization', () => { + it('bounds a grant to its resource and scope', () => { + expect(authorizationGrant(SOURCE_RESOURCE, 'source')).toEqual({ + resource: SOURCE_RESOURCE, + scope: 'source', + }) + expect(authorizationGrant(IDENTITY_RESOURCE, 'identity')).toEqual({ + resource: IDENTITY_RESOURCE, + scope: 'identity', + }) + expect(() => authorizationGrant(SOURCE_RESOURCE, 'api')).toThrow( + 'invalid_scope', + ) + expect(() => authorizationGrant('https://evil.test/mcp', 'source')).toThrow( + 'invalid_target', + ) + expect(authorizationGrant(undefined, undefined)).toEqual({ + resource: null, + scope: 'api', + }) + }) + it('pre-registers only the Source callback', async () => { + expect( + await validateOAuthClientRedirectUri( + 'tanstack-source', + 'https://source.tanstack.com/auth/tanstack/callback', + ), + ).toBe(true) + expect( + await validateOAuthClientRedirectUri( + 'tanstack-source', + 'https://evil.test/auth/tanstack/callback', + ), + ).toBe(false) + }) + it('matches registered redirects exactly and refuses fragments', async () => { + const client = await createRegisteredClientId({ + clientName: 'Test', + redirectUris: ['https://client.test/callback'], + }) + expect( + await validateOAuthClientRedirectUri( + client, + 'https://client.test/callback', + ), + ).toBe(true) + expect( + await validateOAuthClientRedirectUri( + client, + 'https://client.test/callback/', + ), + ).toBe(false) + expect(validateRedirectUri('https://client.test/callback#fragment')).toBe( + false, + ) + }) + it('requires RFC 7636 verifiers', async () => { + const verifier = 'a'.repeat(43) + const digest = await crypto.subtle.digest( + 'SHA-256', + new TextEncoder().encode(verifier), + ) + const challenge = Buffer.from(digest).toString('base64url') + expect(await verifyCodeChallenge(verifier, challenge)).toBe(true) + expect(await verifyCodeChallenge('short', challenge)).toBe(false) + }) +}) diff --git a/harness-tests/postgres/site-baseline.sql b/harness-tests/postgres/site-baseline.sql index 976726eb7..95bde0e0c 100644 --- a/harness-tests/postgres/site-baseline.sql +++ b/harness-tests/postgres/site-baseline.sql @@ -20,3 +20,39 @@ CREATE TABLE role_assignments ( user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, role_id uuid NOT NULL REFERENCES roles(id) ON DELETE CASCADE ); + +-- Existing site OAuth tables predate the migration journal. New OAuth +-- migrations must also run against this disposable site boundary. +CREATE TABLE oauth_mcp_authorization_codes ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + code_hash varchar(64) UNIQUE NOT NULL, + user_id uuid REFERENCES users(id), + client_id varchar(255) NOT NULL, + redirect_uri text NOT NULL, + code_challenge varchar(128) NOT NULL, + code_challenge_method varchar(8) NOT NULL DEFAULT 'S256', + scope text NOT NULL DEFAULT 'api', + expires_at timestamptz NOT NULL, + created_at timestamptz NOT NULL DEFAULT now() +); + +CREATE TABLE oauth_mcp_access_tokens ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + token_hash varchar(64) UNIQUE NOT NULL, + user_id uuid REFERENCES users(id), + client_id varchar(255) NOT NULL, + scope text NOT NULL DEFAULT 'api', + expires_at timestamptz NOT NULL, + last_used_at timestamptz, + created_at timestamptz NOT NULL DEFAULT now() +); + +CREATE TABLE oauth_mcp_refresh_tokens ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + token_hash varchar(64) UNIQUE NOT NULL, + user_id uuid REFERENCES users(id), + client_id varchar(255) NOT NULL, + access_token_id uuid REFERENCES oauth_mcp_access_tokens(id) ON DELETE SET NULL, + expires_at timestamptz NOT NULL, + created_at timestamptz NOT NULL DEFAULT now() +); diff --git a/src/auth/oauth-endpoints.server.ts b/src/auth/oauth-endpoints.server.ts new file mode 100644 index 000000000..bbfb1200c --- /dev/null +++ b/src/auth/oauth-endpoints.server.ts @@ -0,0 +1,104 @@ +import { getHostRuntimeEnv } from '~/server/runtime/host.server' +import { timingSafeEqual } from 'node:crypto' +import { db } from '~/db/client' +import { users } from '~/db/schema' +import { eq } from 'drizzle-orm' +import { envFunctions } from '~/utils/env.functions' +import { readTextBody } from '~/utils/api-boundary.server' +import { + introspectSourceToken, + revokeOAuthGrant, + validateOAuthToken, + verifiedGitHubIdentity, +} from './oauthClient.server' +import { IDENTITY_RESOURCE, SOURCE_RESOURCE } from './oauth-resources' + +const headers = { + 'Cache-Control': 'no-store', + Pragma: 'no-cache', + 'Access-Control-Allow-Origin': '*', +} +const json = (value: unknown, status = 200) => + Response.json(value, { status, headers }) + +async function form(request: Request) { + if ( + !request.headers + .get('content-type') + ?.startsWith('application/x-www-form-urlencoded') + ) + return null + const result = await readTextBody(request, 16 * 1024) + return result.success ? new URLSearchParams(result.text) : null +} + +export async function handleIntrospection(request: Request) { + const host = await getHostRuntimeEnv() + const runtimeSecret = host?.TANSTACK_SOURCE_INTROSPECTION_SECRET + const secret = + typeof runtimeSecret === 'string' + ? runtimeSecret + : envFunctions.TANSTACK_SOURCE_INTROSPECTION_SECRET + const authorization = request.headers.get('authorization') ?? '' + const expected = `Bearer ${secret}` + if ( + !secret || + Buffer.byteLength(authorization) !== Buffer.byteLength(expected) || + !timingSafeEqual(Buffer.from(authorization), Buffer.from(expected)) + ) + return json({ error: 'invalid_client' }, 401) + const params = await form(request) + if ( + !params || + params.get('resource') !== SOURCE_RESOURCE || + !params.get('token') + ) + return json({ error: 'invalid_request' }, 400) + return json(await introspectSourceToken(params.get('token') ?? '')) +} + +export async function handleUserInfo(request: Request) { + const match = /^Bearer ([^\s]+)$/.exec( + request.headers.get('authorization') ?? '', + ) + if (!match) return json({ error: 'invalid_token' }, 401) + const result = await validateOAuthToken(match[1], IDENTITY_RESOURCE) + if (!result.success || result.scope !== 'identity') + return json({ error: 'invalid_token' }, 401) + const [person] = await db + .select({ + sub: users.id, + email: users.email, + name: users.name, + preferred_username: users.displayUsername, + }) + .from(users) + .where(eq(users.id, result.userId)) + .limit(1) + if (!person) return json({ error: 'invalid_token' }, 401) + return json({ + ...person, + github_id: await verifiedGitHubIdentity(result.userId), + }) +} + +export async function handleRevocation(request: Request) { + const params = await form(request) + if (!params?.get('token') || !params.get('client_id')) + return json({ error: 'invalid_request' }, 400) + await revokeOAuthGrant( + params.get('token') ?? '', + params.get('client_id') ?? '', + ) + return new Response(null, { status: 200, headers }) +} + +export const oauthOptions = () => + new Response(null, { + status: 204, + headers: { + ...headers, + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + }) diff --git a/src/auth/oauth-resources.ts b/src/auth/oauth-resources.ts new file mode 100644 index 000000000..9c07a0ca4 --- /dev/null +++ b/src/auth/oauth-resources.ts @@ -0,0 +1,28 @@ +// Resource identities are exact, including the path. Existing grants keep their legacy null audience. +export const SOURCE_RESOURCE = 'https://source.tanstack.com/mcp' +export const IDENTITY_RESOURCE = 'https://tanstack.com/oauth/userinfo' +export const SOURCE_CLIENT = 'tanstack-source' +export const SOURCE_CALLBACK = + 'https://source.tanstack.com/auth/tanstack/callback' + +export function authorizationGrant( + resource?: string | null, + scope?: string | null, +) { + if ( + resource && + resource !== SOURCE_RESOURCE && + resource !== IDENTITY_RESOURCE && + resource !== 'https://tanstack.com/mcp' + ) { + throw new Error('invalid_target') + } + const expected = + resource === SOURCE_RESOURCE + ? 'source' + : resource === IDENTITY_RESOURCE + ? 'identity' + : 'api' + if (scope && scope !== expected) throw new Error('invalid_scope') + return { resource: resource || null, scope: expected } +} diff --git a/src/auth/oauth-session-hint.server.ts b/src/auth/oauth-session-hint.server.ts new file mode 100644 index 000000000..fda5822db --- /dev/null +++ b/src/auth/oauth-session-hint.server.ts @@ -0,0 +1,45 @@ +import { getAuthService } from './index.server' +import { SITE_URL } from '~/utils/site' + +const SOURCE_ORIGIN = 'https://source.tanstack.com' + +function avatarUrl(value: string | null) { + if (!value) return null + try { + const url = new URL(value, SITE_URL) + return url.protocol === 'https:' && !url.username && !url.password + ? url.href + : null + } catch { + return null + } +} + +/** A display hint for Source, never an authentication assertion. No cookie or token leaves this server. */ +export async function handleSessionHint(request: Request) { + const headers = new Headers({ + 'Cache-Control': 'private, no-store', + Pragma: 'no-cache', + Vary: 'Origin, Cookie', + 'Cross-Origin-Resource-Policy': 'same-site', + }) + if (request.headers.get('origin') !== SOURCE_ORIGIN) + return new Response(null, { status: 403, headers }) + headers.set('Access-Control-Allow-Origin', SOURCE_ORIGIN) + headers.set('Access-Control-Allow-Credentials', 'true') + headers.set('Access-Control-Allow-Methods', 'GET, OPTIONS') + if (request.method === 'OPTIONS') + return new Response(null, { status: 204, headers }) + const user = await getAuthService().getCurrentUser(request) + const account = user + ? { + name: ( + user.name?.trim() || + user.displayUsername?.trim() || + user.email.split('@')[0] + ).slice(0, 120), + avatar: avatarUrl(user.image || user.oauthImage), + } + : null + return Response.json({ account }, { headers }) +} diff --git a/src/auth/oauthClient.server.ts b/src/auth/oauthClient.server.ts index 1133db4b7..78c04196f 100644 --- a/src/auth/oauthClient.server.ts +++ b/src/auth/oauthClient.server.ts @@ -1,10 +1,18 @@ +import { getHostRuntimeEnv } from '~/server/runtime/host.server' +import { timingSafeEqual } from 'node:crypto' import { db } from '~/db/client' import { oauthAuthorizationCodes, oauthAccessTokens, oauthRefreshTokens, + oauthAccounts, } from '~/db/schema' -import { eq, and, sql, lt } from 'drizzle-orm' +import { eq, and, sql, lt, isNull } from 'drizzle-orm' +import { + authorizationGrant, + SOURCE_CLIENT, + SOURCE_CALLBACK, +} from './oauth-resources' import { sha256Hex } from '~/utils/hash' import { envFunctions } from '~/utils/env.functions' @@ -30,6 +38,7 @@ export type OAuthValidationResult = tokenId: string userId: string clientId: string + scope: string } | { success: false @@ -83,8 +92,13 @@ function base64UrlToString(value: string): string | null { } } -function getOAuthClientSigningSecret() { - const secret = envFunctions.SESSION_SECRET +async function getOAuthClientSigningSecret() { + const host = await getHostRuntimeEnv() + const runtimeSecret = host?.SESSION_SECRET + const secret = + typeof runtimeSecret === 'string' + ? runtimeSecret + : envFunctions.SESSION_SECRET if (!secret && process.env.NODE_ENV === 'production') { throw new Error('SESSION_SECRET is required for OAuth client registration') } @@ -95,7 +109,7 @@ function getOAuthClientSigningSecret() { async function signOAuthClientPayload(payload: string) { const key = await crypto.subtle.importKey( 'raw', - new TextEncoder().encode(getOAuthClientSigningSecret()), + new TextEncoder().encode(await getOAuthClientSigningSecret()), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'], @@ -110,7 +124,7 @@ async function signOAuthClientPayload(payload: string) { function parseRegisteredClientPayload( value: unknown, -): { clientName: string; redirectUris: Array } | null { +): { clientName: string; redirectUris: Array; exact: boolean } | null { if (!isRecord(value)) return null const clientName = value.clientName @@ -129,7 +143,7 @@ function parseRegisteredClientPayload( return null } - return { clientName, redirectUris } + return { clientName, redirectUris, exact: value.exact === true } } export async function createRegisteredClientId(params: { @@ -138,9 +152,8 @@ export async function createRegisteredClientId(params: { }) { const payload = JSON.stringify({ clientName: params.clientName, - redirectUris: params.redirectUris - .map(normalizeRedirectUri) - .filter((uri) => uri !== null), + exact: true, + redirectUris: params.redirectUris, }) const encodedPayload = stringToBase64Url(payload) const signature = await signOAuthClientPayload(encodedPayload) @@ -152,22 +165,27 @@ export async function validateOAuthClientRedirectUri( clientId: string, redirectUri: string, ) { + if (clientId === SOURCE_CLIENT) return redirectUri === SOURCE_CALLBACK if (!clientId.startsWith(REGISTERED_CLIENT_PREFIX)) { return false } const body = clientId.slice(REGISTERED_CLIENT_PREFIX.length) - const [encodedPayload, signature] = body.split('.') + const [encodedPayload, signature, extra] = body.split('.') if ( !encodedPayload || !signature || + extra !== undefined || encodedPayload.length > MAX_REGISTERED_CLIENT_PAYLOAD_LENGTH ) { return false } const expectedSignature = await signOAuthClientPayload(encodedPayload) - if (expectedSignature !== signature) { + if ( + Buffer.byteLength(expectedSignature) !== Buffer.byteLength(signature) || + !timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(signature)) + ) { return false } @@ -188,7 +206,9 @@ export async function validateOAuthClientRedirectUri( return ( !!metadata && !!normalizedRedirectUri && - metadata.redirectUris.includes(normalizedRedirectUri) + metadata.redirectUris.includes( + metadata.exact ? redirectUri : normalizedRedirectUri, + ) ) } @@ -215,7 +235,7 @@ export const hashToken = sha256Hex export function validateRedirectUri(uri: string): boolean { try { const url = new URL(uri) - if (url.username || url.password) { + if (url.username || url.password || url.hash) { return false } @@ -247,6 +267,7 @@ export async function verifyCodeChallenge( codeVerifier: string, codeChallenge: string, ): Promise { + if (!/^[A-Za-z0-9._~-]{43,128}$/.test(codeVerifier)) return false const encoder = new TextEncoder() const data = encoder.encode(codeVerifier) const hashBuffer = await crypto.subtle.digest('SHA-256', data) @@ -291,6 +312,7 @@ export async function createAuthorizationCode(params: { codeChallenge: string codeChallengeMethod?: string scope?: string + resource?: string }): Promise { if ( !(await validateOAuthClientRedirectUri(params.clientId, params.redirectUri)) @@ -298,6 +320,14 @@ export async function createAuthorizationCode(params: { throw new Error('Client is not registered for this redirect URI') } + if ( + params.codeChallengeMethod !== undefined && + params.codeChallengeMethod !== 'S256' + ) + throw new Error('invalid_request') + if (!/^[A-Za-z0-9_-]{43}$/.test(params.codeChallenge)) + throw new Error('invalid_request') + const grant = authorizationGrant(params.resource, params.scope) const code = generateToken('') const codeHash = await hashToken(code) const expiresAt = new Date(Date.now() + AUTH_CODE_TTL_MS) @@ -309,7 +339,8 @@ export async function createAuthorizationCode(params: { redirectUri: params.redirectUri, codeChallenge: params.codeChallenge, codeChallengeMethod: params.codeChallengeMethod || 'S256', - scope: params.scope || 'api', + scope: grant.scope, + resource: grant.resource, expiresAt, }) @@ -323,6 +354,8 @@ export async function exchangeAuthorizationCode(params: { code: string codeVerifier: string redirectUri: string + clientId?: string | null + resource?: string | null }): Promise< | { success: true @@ -353,6 +386,13 @@ export async function exchangeAuthorizationCode(params: { return { success: false, error: 'invalid_grant' } } + if ( + (authCode.resource ?? null) !== (params.resource || null) || + (params.clientId && params.clientId !== authCode.clientId) || + (authCode.resource && !params.clientId) + ) + return { success: false, error: 'invalid_grant' } + // Validate redirect URI matches if (authCode.redirectUri !== params.redirectUri) { return { success: false, error: 'invalid_grant' } @@ -376,6 +416,7 @@ export async function exchangeAuthorizationCode(params: { const accessExpiresAt = new Date(Date.now() + ACCESS_TOKEN_TTL_MS) const refreshExpiresAt = new Date(Date.now() + REFRESH_TOKEN_TTL_MS) + const grantId = crypto.randomUUID() // Insert access token const accessTokenResult = await db .insert(oauthAccessTokens) @@ -384,6 +425,8 @@ export async function exchangeAuthorizationCode(params: { userId: authCode.userId, clientId: authCode.clientId, scope: authCode.scope, + resource: authCode.resource, + grantId, expiresAt: accessExpiresAt, }) .returning({ id: oauthAccessTokens.id }) @@ -394,6 +437,9 @@ export async function exchangeAuthorizationCode(params: { userId: authCode.userId, clientId: authCode.clientId, accessTokenId: accessTokenResult[0].id, + scope: authCode.scope, + resource: authCode.resource, + grantId, expiresAt: refreshExpiresAt, }) @@ -409,10 +455,14 @@ export async function exchangeAuthorizationCode(params: { /** * Refresh an access token */ -export async function refreshAccessToken(refreshToken: string): Promise< +export async function refreshAccessToken( + refreshToken: string, + binding?: { clientId: string | null; resource: string | null }, +): Promise< | { success: true accessToken: string + refreshToken?: string expiresIn: number scope: string } @@ -433,6 +483,14 @@ export async function refreshAccessToken(refreshToken: string): Promise< return { success: false, error: 'invalid_grant' } } + if ( + (token.resource ?? null) !== (binding?.resource || null) || + (binding?.clientId && binding.clientId !== token.clientId) || + (token.resource && !binding?.clientId) + ) + return { success: false, error: 'invalid_grant' } + if (token.resource) return rotateResourceToken(tokenHash) + // Check expiration if (token.expiresAt < new Date()) { await db @@ -454,6 +512,7 @@ export async function refreshAccessToken(refreshToken: string): Promise< userId: token.userId, clientId: token.clientId, scope: 'api', + grantId: token.grantId, expiresAt: accessExpiresAt, }) .returning({ id: oauthAccessTokens.id }) @@ -478,6 +537,7 @@ export async function refreshAccessToken(refreshToken: string): Promise< */ export async function validateOAuthToken( token: string, + resource: string | null = null, ): Promise { const tokenHash = await hashToken(token) @@ -488,6 +548,8 @@ export async function validateOAuthToken( clientId: oauthAccessTokens.clientId, expiresAt: oauthAccessTokens.expiresAt, lastUsedAt: oauthAccessTokens.lastUsedAt, + resource: oauthAccessTokens.resource, + scope: oauthAccessTokens.scope, }) .from(oauthAccessTokens) .where(eq(oauthAccessTokens.tokenHash, tokenHash)) @@ -495,7 +557,12 @@ export async function validateOAuthToken( const accessToken = result[0] - if (!accessToken) { + const audienceMatches = + accessToken && + ((accessToken.resource ?? null) === resource || + (resource === null && + accessToken.resource === 'https://tanstack.com/mcp')) + if (!accessToken || !audienceMatches) { return { success: false, error: 'Invalid access token', @@ -527,6 +594,7 @@ export async function validateOAuthToken( tokenId: accessToken.id, userId: accessToken.userId, clientId: accessToken.clientId, + scope: accessToken.scope, } } @@ -537,6 +605,7 @@ export async function validateOAuthToken( export async function listConnectedApps(userId: string): Promise< Array<{ clientId: string + clientName: string createdAt: string lastUsedAt: string | null }> @@ -553,6 +622,7 @@ export async function listConnectedApps(userId: string): Promise< return result.map((r) => ({ clientId: r.clientId, + clientName: oauthClientName(r.clientId), createdAt: r.createdAt, lastUsedAt: null, })) @@ -565,25 +635,40 @@ export async function revokeTokensForClient( userId: string, clientId: string, ): Promise { - // Delete refresh tokens first (they reference access tokens) - await db - .delete(oauthRefreshTokens) - .where( - and( - eq(oauthRefreshTokens.userId, userId), - eq(oauthRefreshTokens.clientId, clientId), - ), - ) - - // Delete access tokens - await db - .delete(oauthAccessTokens) - .where( - and( - eq(oauthAccessTokens.userId, userId), - eq(oauthAccessTokens.clientId, clientId), - ), - ) + await db.transaction(async (tx) => { + const grants = await tx + .select({ grantId: oauthRefreshTokens.grantId }) + .from(oauthRefreshTokens) + .where( + and( + eq(oauthRefreshTokens.userId, userId), + eq(oauthRefreshTokens.clientId, clientId), + ), + ) + for (const grantId of [ + ...new Set(grants.map((g) => g.grantId).filter((g) => g !== null)), + ].sort()) { + await tx.execute( + sql`select pg_advisory_xact_lock(hashtextextended(${grantId}, 0))`, + ) + } + await tx + .delete(oauthRefreshTokens) + .where( + and( + eq(oauthRefreshTokens.userId, userId), + eq(oauthRefreshTokens.clientId, clientId), + ), + ) + await tx + .delete(oauthAccessTokens) + .where( + and( + eq(oauthAccessTokens.userId, userId), + eq(oauthAccessTokens.clientId, clientId), + ), + ) + }) } /** @@ -605,3 +690,194 @@ export async function cleanupExpiredTokens(): Promise { // Delete expired access tokens await db.delete(oauthAccessTokens).where(lt(oauthAccessTokens.expiresAt, now)) } + +// Row locking ensures a refresh token can only be consumed once, even by concurrent requests. +async function rotateResourceToken(tokenHash: string): Promise< + | { + success: true + accessToken: string + refreshToken: string + scope: string + expiresIn: number + } + | { success: false; error: string } +> { + return db.transaction(async (tx) => { + const [candidate] = await tx + .select({ grantId: oauthRefreshTokens.grantId }) + .from(oauthRefreshTokens) + .where(eq(oauthRefreshTokens.tokenHash, tokenHash)) + .limit(1) + if (!candidate?.grantId) return { success: false, error: 'invalid_grant' } + await tx.execute( + sql`select pg_advisory_xact_lock(hashtextextended(${candidate.grantId}, 0))`, + ) + const [token] = await tx + .select() + .from(oauthRefreshTokens) + .where(eq(oauthRefreshTokens.tokenHash, tokenHash)) + .for('update') + if (!token || !token.grantId) + return { success: false, error: 'invalid_grant' } + if (token.consumedAt || token.expiresAt <= new Date()) { + await tx + .delete(oauthAccessTokens) + .where(eq(oauthAccessTokens.grantId, token.grantId)) + await tx + .delete(oauthRefreshTokens) + .where(eq(oauthRefreshTokens.grantId, token.grantId)) + return { success: false, error: 'invalid_grant' } + } + const accessToken = generateToken(ACCESS_TOKEN_PREFIX) + const refreshToken = generateToken(REFRESH_TOKEN_PREFIX) + await tx + .update(oauthRefreshTokens) + .set({ consumedAt: new Date() }) + .where( + and( + eq(oauthRefreshTokens.id, token.id), + isNull(oauthRefreshTokens.consumedAt), + ), + ) + const [access] = await tx + .insert(oauthAccessTokens) + .values({ + tokenHash: await hashToken(accessToken), + userId: token.userId, + clientId: token.clientId, + resource: token.resource, + scope: token.scope, + grantId: token.grantId, + expiresAt: new Date(Date.now() + ACCESS_TOKEN_TTL_MS), + }) + .returning({ id: oauthAccessTokens.id }) + await tx.insert(oauthRefreshTokens).values({ + tokenHash: await hashToken(refreshToken), + userId: token.userId, + clientId: token.clientId, + resource: token.resource, + scope: token.scope, + grantId: token.grantId, + accessTokenId: access.id, + expiresAt: token.expiresAt, + }) + return { + success: true, + accessToken, + refreshToken, + scope: token.scope, + expiresIn: ACCESS_TOKEN_TTL_MS / 1000, + } + }) +} + +export async function introspectSourceToken(token: string) { + const [row] = await db + .select({ + sub: oauthAccessTokens.userId, + aud: oauthAccessTokens.resource, + scope: oauthAccessTokens.scope, + exp: oauthAccessTokens.expiresAt, + client_id: oauthAccessTokens.clientId, + }) + .from(oauthAccessTokens) + .where(eq(oauthAccessTokens.tokenHash, await hashToken(token))) + .limit(1) + if ( + !row || + row.aud !== 'https://source.tanstack.com/mcp' || + row.exp <= new Date() || + row.scope !== 'source' + ) + return { active: false } + return { + ...row, + github_id: await verifiedGitHubIdentity(row.sub), + active: true, + iss: 'https://tanstack.com', + exp: Math.floor(row.exp.getTime() / 1000), + } +} + +export async function revokeOAuthGrant(token: string, clientId: string) { + const hash = await hashToken(token) + const [access] = await db + .select() + .from(oauthAccessTokens) + .where( + and( + eq(oauthAccessTokens.tokenHash, hash), + eq(oauthAccessTokens.clientId, clientId), + ), + ) + .limit(1) + const [refresh] = await db + .select() + .from(oauthRefreshTokens) + .where( + and( + eq(oauthRefreshTokens.tokenHash, hash), + eq(oauthRefreshTokens.clientId, clientId), + ), + ) + .limit(1) + const grantId = access?.grantId ?? refresh?.grantId + if (!grantId) return + await db.transaction(async (tx) => { + await tx.execute( + sql`select pg_advisory_xact_lock(hashtextextended(${grantId}, 0))`, + ) + await tx + .delete(oauthAccessTokens) + .where(eq(oauthAccessTokens.grantId, grantId)) + await tx + .delete(oauthRefreshTokens) + .where(eq(oauthRefreshTokens.grantId, grantId)) + }) +} + +export async function authorizationClientName( + clientId: string, + redirectUri: string, +) { + if (!(await validateOAuthClientRedirectUri(clientId, redirectUri))) + throw new Error('invalid_client') + if (clientId === SOURCE_CLIENT) return 'TanStack Source' + const payload = base64UrlToString( + clientId.slice(REGISTERED_CLIENT_PREFIX.length).split('.')[0], + ) + const metadata = payload + ? parseRegisteredClientPayload(JSON.parse(payload)) + : null + return metadata?.clientName ?? 'OAuth client' +} + +function oauthClientName(clientId: string) { + if (clientId === SOURCE_CLIENT) return 'TanStack Source' + try { + const payload = base64UrlToString( + clientId.slice(REGISTERED_CLIENT_PREFIX.length).split('.')[0], + ) + const metadata = payload + ? parseRegisteredClientPayload(JSON.parse(payload)) + : null + return metadata?.clientName ?? clientId + } catch { + return clientId + } +} + +/** Only an unambiguous GitHub identity can automatically resolve an account in another product. */ +export async function verifiedGitHubIdentity(userId: string) { + const accounts = await db + .select({ id: oauthAccounts.providerAccountId }) + .from(oauthAccounts) + .where( + and( + eq(oauthAccounts.userId, userId), + eq(oauthAccounts.provider, 'github'), + ), + ) + .limit(2) + return accounts.length === 1 ? accounts[0].id : null +} diff --git a/src/db/schema.ts b/src/db/schema.ts index 54a0fd536..2474319e9 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -928,7 +928,8 @@ export const oauthAuthorizationCodes = pgTable( userId: uuid('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), - clientId: varchar('client_id', { length: 255 }).notNull(), + clientId: text('client_id').notNull(), + resource: text('resource'), redirectUri: text('redirect_uri').notNull(), codeChallenge: varchar('code_challenge', { length: 128 }).notNull(), codeChallengeMethod: varchar('code_challenge_method', { length: 8 }) @@ -973,7 +974,9 @@ export const oauthAccessTokens = pgTable( userId: uuid('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), - clientId: varchar('client_id', { length: 255 }).notNull(), + clientId: text('client_id').notNull(), + resource: text('resource'), + grantId: uuid('grant_id'), scope: text('scope').notNull().default('api'), expiresAt: timestamp('expires_at', { withTimezone: true, @@ -985,6 +988,7 @@ export const oauthAccessTokens = pgTable( .defaultNow(), }, (table) => ({ + grantIdx: index('oauth_access_grant_idx').on(table.grantId), tokenHashIdx: index('oauth_mcp_access_tokens_hash_idx').on(table.tokenHash), userIdIdx: index('oauth_mcp_access_tokens_user_idx').on(table.userId), expiresAtIdx: index('oauth_mcp_access_tokens_expires_idx').on( @@ -1013,7 +1017,11 @@ export const oauthRefreshTokens = pgTable( userId: uuid('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), - clientId: varchar('client_id', { length: 255 }).notNull(), + clientId: text('client_id').notNull(), + resource: text('resource'), + scope: text('scope').notNull().default('api'), + consumedAt: timestamp('consumed_at', { withTimezone: true, mode: 'date' }), + grantId: uuid('grant_id'), accessTokenId: uuid('access_token_id').references( () => oauthAccessTokens.id, { onDelete: 'set null' }, @@ -1027,6 +1035,7 @@ export const oauthRefreshTokens = pgTable( .defaultNow(), }, (table) => ({ + grantIdx: index('oauth_refresh_grant_idx').on(table.grantId), tokenHashIdx: index('oauth_mcp_refresh_tokens_hash_idx').on( table.tokenHash, ), diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 7dd6c081e..8a710b88d 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -112,8 +112,12 @@ import { Route as DsTakeoverRouteImport } from './routes/ds.takeover' import { Route as DsTypographyRouteImport } from './routes/ds.typography' import { Route as LibrariesFrameworkRouteImport } from './routes/libraries_.$framework' import { Route as OauthAuthorizeRouteImport } from './routes/oauth/authorize' +import { Route as OauthIntrospectRouteImport } from './routes/oauth/introspect' import { Route as OauthRegisterRouteImport } from './routes/oauth/register' +import { Route as OauthRevokeRouteImport } from './routes/oauth/revoke' +import { Route as OauthSessionHintRouteImport } from './routes/oauth/session-hint' import { Route as OauthTokenRouteImport } from './routes/oauth/token' +import { Route as OauthUserinfoRouteImport } from './routes/oauth/userinfo' import { Route as PartnersIndexRouteImport } from './routes/partners.index' import { Route as PartnersPartnerRouteImport } from './routes/partners.$partner' import { Route as PartnersNetlifyRouteImport } from './routes/partners.netlify' @@ -844,16 +848,36 @@ const OauthAuthorizeRoute = OauthAuthorizeRouteImport.update({ path: '/oauth/authorize', getParentRoute: () => rootRouteImport, } as any) +const OauthIntrospectRoute = OauthIntrospectRouteImport.update({ + id: '/oauth/introspect', + path: '/oauth/introspect', + getParentRoute: () => rootRouteImport, +} as any) const OauthRegisterRoute = OauthRegisterRouteImport.update({ id: '/oauth/register', path: '/oauth/register', getParentRoute: () => rootRouteImport, } as any) +const OauthRevokeRoute = OauthRevokeRouteImport.update({ + id: '/oauth/revoke', + path: '/oauth/revoke', + getParentRoute: () => rootRouteImport, +} as any) +const OauthSessionHintRoute = OauthSessionHintRouteImport.update({ + id: '/oauth/session-hint', + path: '/oauth/session-hint', + getParentRoute: () => rootRouteImport, +} as any) const OauthTokenRoute = OauthTokenRouteImport.update({ id: '/oauth/token', path: '/oauth/token', getParentRoute: () => rootRouteImport, } as any) +const OauthUserinfoRoute = OauthUserinfoRouteImport.update({ + id: '/oauth/userinfo', + path: '/oauth/userinfo', + getParentRoute: () => rootRouteImport, +} as any) const PartnersIndexRoute = PartnersIndexRouteImport.update({ id: '/', path: '/', @@ -2115,8 +2139,12 @@ export interface FileRoutesByFullPath { '/ds/typography': typeof DsTypographyRoute '/libraries/$framework': typeof LibrariesFrameworkRoute '/oauth/authorize': typeof OauthAuthorizeRoute + '/oauth/introspect': typeof OauthIntrospectRoute '/oauth/register': typeof OauthRegisterRoute + '/oauth/revoke': typeof OauthRevokeRoute + '/oauth/session-hint': typeof OauthSessionHintRoute '/oauth/token': typeof OauthTokenRoute + '/oauth/userinfo': typeof OauthUserinfoRoute '/partners/$partner': typeof PartnersPartnerRoute '/partners/netlify': typeof PartnersNetlifyRoute '/partners/railway': typeof PartnersRailwayRoute @@ -2424,8 +2452,12 @@ export interface FileRoutesByTo { '/ds/typography': typeof DsTypographyRoute '/libraries/$framework': typeof LibrariesFrameworkRoute '/oauth/authorize': typeof OauthAuthorizeRoute + '/oauth/introspect': typeof OauthIntrospectRoute '/oauth/register': typeof OauthRegisterRoute + '/oauth/revoke': typeof OauthRevokeRoute + '/oauth/session-hint': typeof OauthSessionHintRoute '/oauth/token': typeof OauthTokenRoute + '/oauth/userinfo': typeof OauthUserinfoRoute '/partners/$partner': typeof PartnersPartnerRoute '/partners/netlify': typeof PartnersNetlifyRoute '/partners/railway': typeof PartnersRailwayRoute @@ -2740,8 +2772,12 @@ export interface FileRoutesById { '/ds/typography': typeof DsTypographyRoute '/libraries_/$framework': typeof LibrariesFrameworkRoute '/oauth/authorize': typeof OauthAuthorizeRoute + '/oauth/introspect': typeof OauthIntrospectRoute '/oauth/register': typeof OauthRegisterRoute + '/oauth/revoke': typeof OauthRevokeRoute + '/oauth/session-hint': typeof OauthSessionHintRoute '/oauth/token': typeof OauthTokenRoute + '/oauth/userinfo': typeof OauthUserinfoRoute '/partners/$partner': typeof PartnersPartnerRoute '/partners/netlify': typeof PartnersNetlifyRoute '/partners/railway': typeof PartnersRailwayRoute @@ -3060,8 +3096,12 @@ export interface FileRouteTypes { | '/ds/typography' | '/libraries/$framework' | '/oauth/authorize' + | '/oauth/introspect' | '/oauth/register' + | '/oauth/revoke' + | '/oauth/session-hint' | '/oauth/token' + | '/oauth/userinfo' | '/partners/$partner' | '/partners/netlify' | '/partners/railway' @@ -3369,8 +3409,12 @@ export interface FileRouteTypes { | '/ds/typography' | '/libraries/$framework' | '/oauth/authorize' + | '/oauth/introspect' | '/oauth/register' + | '/oauth/revoke' + | '/oauth/session-hint' | '/oauth/token' + | '/oauth/userinfo' | '/partners/$partner' | '/partners/netlify' | '/partners/railway' @@ -3684,8 +3728,12 @@ export interface FileRouteTypes { | '/ds/typography' | '/libraries_/$framework' | '/oauth/authorize' + | '/oauth/introspect' | '/oauth/register' + | '/oauth/revoke' + | '/oauth/session-hint' | '/oauth/token' + | '/oauth/userinfo' | '/partners/$partner' | '/partners/netlify' | '/partners/railway' @@ -3957,8 +4005,12 @@ export interface RootRouteChildren { BuilderNewRoute: typeof BuilderNewRoute LibrariesFrameworkRoute: typeof LibrariesFrameworkRoute OauthAuthorizeRoute: typeof OauthAuthorizeRoute + OauthIntrospectRoute: typeof OauthIntrospectRoute OauthRegisterRoute: typeof OauthRegisterRoute + OauthRevokeRoute: typeof OauthRevokeRoute + OauthSessionHintRoute: typeof OauthSessionHintRoute OauthTokenRoute: typeof OauthTokenRoute + OauthUserinfoRoute: typeof OauthUserinfoRoute ShowcaseIdRoute: typeof ShowcaseIdRoute ShowcaseSubmitRoute: typeof ShowcaseSubmitRoute StackCategoryRoute: typeof StackCategoryRoute @@ -4780,6 +4832,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof OauthAuthorizeRouteImport parentRoute: typeof rootRouteImport } + '/oauth/introspect': { + id: '/oauth/introspect' + path: '/oauth/introspect' + fullPath: '/oauth/introspect' + preLoaderRoute: typeof OauthIntrospectRouteImport + parentRoute: typeof rootRouteImport + } '/oauth/register': { id: '/oauth/register' path: '/oauth/register' @@ -4787,6 +4846,20 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof OauthRegisterRouteImport parentRoute: typeof rootRouteImport } + '/oauth/revoke': { + id: '/oauth/revoke' + path: '/oauth/revoke' + fullPath: '/oauth/revoke' + preLoaderRoute: typeof OauthRevokeRouteImport + parentRoute: typeof rootRouteImport + } + '/oauth/session-hint': { + id: '/oauth/session-hint' + path: '/oauth/session-hint' + fullPath: '/oauth/session-hint' + preLoaderRoute: typeof OauthSessionHintRouteImport + parentRoute: typeof rootRouteImport + } '/oauth/token': { id: '/oauth/token' path: '/oauth/token' @@ -4794,6 +4867,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof OauthTokenRouteImport parentRoute: typeof rootRouteImport } + '/oauth/userinfo': { + id: '/oauth/userinfo' + path: '/oauth/userinfo' + fullPath: '/oauth/userinfo' + preLoaderRoute: typeof OauthUserinfoRouteImport + parentRoute: typeof rootRouteImport + } '/partners/': { id: '/partners/' path: '/' @@ -7170,8 +7250,12 @@ const rootRouteChildren: RootRouteChildren = { BuilderNewRoute: BuilderNewRoute, LibrariesFrameworkRoute: LibrariesFrameworkRoute, OauthAuthorizeRoute: OauthAuthorizeRoute, + OauthIntrospectRoute: OauthIntrospectRoute, OauthRegisterRoute: OauthRegisterRoute, + OauthRevokeRoute: OauthRevokeRoute, + OauthSessionHintRoute: OauthSessionHintRoute, OauthTokenRoute: OauthTokenRoute, + OauthUserinfoRoute: OauthUserinfoRoute, ShowcaseIdRoute: ShowcaseIdRoute, ShowcaseSubmitRoute: ShowcaseSubmitRoute, StackCategoryRoute: StackCategoryRoute, diff --git a/src/routes/[.]well-known/oauth-authorization-server.ts b/src/routes/[.]well-known/oauth-authorization-server.ts index 422df323a..48e6f1558 100644 --- a/src/routes/[.]well-known/oauth-authorization-server.ts +++ b/src/routes/[.]well-known/oauth-authorization-server.ts @@ -16,7 +16,10 @@ export const Route = createFileRoute('/.well-known/oauth-authorization-server')( grant_types_supported: ['authorization_code', 'refresh_token'], code_challenge_methods_supported: ['S256'], token_endpoint_auth_methods_supported: ['none'], - scopes_supported: ['api'], + scopes_supported: ['api', 'identity', 'source'], + revocation_endpoint: `${SITE_URL}/oauth/revoke`, + introspection_endpoint: `${SITE_URL}/oauth/introspect`, + authorization_response_iss_parameter_supported: true, } setResponseHeader('Content-Type', 'application/json') diff --git a/src/routes/account/integrations.tsx b/src/routes/account/integrations.tsx index e2cf9283f..dfe58ae16 100644 --- a/src/routes/account/integrations.tsx +++ b/src/routes/account/integrations.tsx @@ -412,7 +412,7 @@ function IntegrationsPage() {
- {formatClientId(app.clientId)} + {app.clientName || formatClientId(app.clientId)}
diff --git a/src/routes/oauth/authorize.tsx b/src/routes/oauth/authorize.tsx index 1a4182489..1aa3b58df 100644 --- a/src/routes/oauth/authorize.tsx +++ b/src/routes/oauth/authorize.tsx @@ -6,7 +6,11 @@ import { createFileRoute, } from '@tanstack/react-router' import * as v from 'valibot' -import { createAuthorizationCode } from '~/utils/oauthClient.functions' +import { SITE_URL } from '~/utils/site' +import { + createAuthorizationCode, + validateAuthorizationRequest, +} from '~/utils/oauthClient.functions' import { getCurrentUser } from '~/utils/auth.functions' import { Card } from '~/components/Card' import { Button } from '~/ui' @@ -65,6 +69,7 @@ const searchSchema = v.object({ state: v.optional(v.pipe(v.string(), v.maxLength(2048))), scope: v.optional(v.pipe(v.string(), v.maxLength(512))), response_type: v.optional(v.pipe(v.string(), v.maxLength(64))), + resource: v.optional(v.pipe(v.string(), v.maxLength(2048))), }) export const Route = createFileRoute('/oauth/authorize')({ @@ -79,6 +84,7 @@ export const Route = createFileRoute('/oauth/authorize')({ state: search.state, scope: search.scope, response_type: search.response_type, + resource: search.resource, }), loader: async ({ deps }) => { // Validate required params @@ -114,6 +120,27 @@ export const Route = createFileRoute('/oauth/authorize')({ } } + const checked = await validateAuthorizationRequest({ + data: { + clientId: deps.client_id, + redirectUri: deps.redirect_uri, + resource: deps.resource, + scope: deps.scope, + }, + }) + if ( + !checked.valid || + (deps.resource && + (deps.code_challenge_method !== 'S256' || + deps.response_type !== 'code')) || + !/^[A-Za-z0-9_-]{43}$/.test(deps.code_challenge) + ) + return { + error: 'invalid_request', + errorDescription: + 'Check the registered client, resource, scope and S256 challenge.', + } + // Check if user is logged in const user = await getCurrentUser() @@ -123,6 +150,9 @@ export const Route = createFileRoute('/oauth/authorize')({ currentUrl.searchParams.set('client_id', deps.client_id) currentUrl.searchParams.set('redirect_uri', deps.redirect_uri) currentUrl.searchParams.set('code_challenge', deps.code_challenge) + if (deps.resource) currentUrl.searchParams.set('resource', deps.resource) + if (deps.response_type) + currentUrl.searchParams.set('response_type', deps.response_type) if (deps.code_challenge_method) { currentUrl.searchParams.set( 'code_challenge_method', @@ -155,7 +185,9 @@ export const Route = createFileRoute('/oauth/authorize')({ codeChallenge: deps.code_challenge, codeChallengeMethod: deps.code_challenge_method || 'S256', state: deps.state, - scope: deps.scope || 'api', + scope: checked.scope, + resource: deps.resource, + clientName: checked.clientName, } }, }) @@ -212,12 +244,14 @@ function AuthorizePage() { codeChallenge: data.codeChallenge, codeChallengeMethod: data.codeChallengeMethod, scope: data.scope, + resource: data.resource, }, }) // Build redirect URL with code const redirectUrl = new URL(data.redirectUri) redirectUrl.searchParams.set('code', result.code) + redirectUrl.searchParams.set('iss', SITE_URL) if (data.state) { redirectUrl.searchParams.set('state', data.state) } @@ -232,6 +266,7 @@ function AuthorizePage() { // Redirect back with error const redirectUrl = new URL(data.redirectUri) redirectUrl.searchParams.set('error', 'access_denied') + redirectUrl.searchParams.set('iss', SITE_URL) redirectUrl.searchParams.set( 'error_description', 'User denied the authorization request', @@ -243,7 +278,7 @@ function AuthorizePage() { } // Format client ID for display (often looks like a technical identifier) - const displayClientId = data.clientId.replace(/-/g, ' ').replace(/_/g, ' ') + const displayClientId = data.clientName return (
@@ -255,28 +290,36 @@ function AuthorizePage() {

- This application wants to access the TanStack API on your behalf. + {data.scope === 'source' + ? 'This app can read and change TanStack Source repositories with your permissions.' + : data.scope === 'identity' + ? 'Share your TanStack account ID, name, email and linked GitHub identity to sign in.' + : 'This app will access the TanStack API on your behalf.'}

-
-

- This will allow the app to: + {data.scope === 'source' ? ( +

+ Revoke access in your TanStack connected apps.

-
    -
  • - ✓ - Search and read TanStack documentation -
  • -
  • - ✓ - List libraries and get NPM stats -
  • -
  • - ✓ - Get ecosystem partner recommendations -
  • -
-
+ ) : null} + + {data.scope === 'source' ? ( +

+ + Connect your TanStack account in Source + {' '} + before authorizing this client. +

+ ) : null} + +

+ Returns to {data.redirectUri} +