diff --git a/packages/server/drizzle/0084_processed_events_claim_lifecycle.sql b/packages/server/drizzle/0084_processed_events_claim_lifecycle.sql new file mode 100644 index 000000000..577865c45 --- /dev/null +++ b/packages/server/drizzle/0084_processed_events_claim_lifecycle.sql @@ -0,0 +1,14 @@ +-- Custom migration (drizzle-kit generate --custom). +-- +-- `processed_events` predates drizzle-kit management: it was created by the +-- hand-written 0003_feishu_adapter.sql and is intentionally absent from the +-- drizzle schema snapshot (like the adapter_* tables), so a regular +-- `drizzle-kit generate` cannot emit ALTERs for it. The declarative shape +-- lives in src/db/schema/processed-events.ts for documentation parity. +-- +-- Claim lifecycle for webhook event dedup (issue #317): rows gain a +-- pending/done status plus an expiry for pending claims. Existing rows are +-- backfilled to 'done' by the column default so they keep deduping. +ALTER TABLE "processed_events" ADD COLUMN IF NOT EXISTS "status" text DEFAULT 'done' NOT NULL;--> statement-breakpoint +ALTER TABLE "processed_events" ADD COLUMN IF NOT EXISTS "expires_at" timestamp with time zone;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "idx_processed_events_status_expires" ON "processed_events" ("status","expires_at"); diff --git a/packages/server/drizzle/LATEST b/packages/server/drizzle/LATEST index 0a280305d..4272b1792 100644 --- a/packages/server/drizzle/LATEST +++ b/packages/server/drizzle/LATEST @@ -1 +1 @@ -0083_tiresome_bucky +0084_processed_events_claim_lifecycle diff --git a/packages/server/drizzle/meta/0084_snapshot.json b/packages/server/drizzle/meta/0084_snapshot.json new file mode 100644 index 000000000..da36ba107 --- /dev/null +++ b/packages/server/drizzle/meta/0084_snapshot.json @@ -0,0 +1,5850 @@ +{ + "id": "19df43a9-81eb-4985-9640-03ae080b4d6f", + "prevId": "a85cf597-fe4f-431a-941b-dd1b309f6f0f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agent_chat_sessions": { + "name": "agent_chat_sessions", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runtime_state": { + "name": "runtime_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "runtime_state_at": { + "name": "runtime_state_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_agent_chat_sessions_chat_agent": { + "name": "idx_agent_chat_sessions_chat_agent", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "agent_chat_sessions_agent_id_agents_uuid_fk": { + "name": "agent_chat_sessions_agent_id_agents_uuid_fk", + "tableFrom": "agent_chat_sessions", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_chat_sessions_chat_id_chats_id_fk": { + "name": "agent_chat_sessions_chat_id_chats_id_fk", + "tableFrom": "agent_chat_sessions", + "columnsFrom": [ + "chat_id" + ], + "tableTo": "chats", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "agent_chat_sessions_agent_id_chat_id_pk": { + "name": "agent_chat_sessions_agent_id_chat_id_pk", + "columns": [ + "agent_id", + "chat_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_configs": { + "name": "agent_configs", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_presence": { + "name": "agent_presence", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'offline'" + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_type": { + "name": "runtime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_version": { + "name": "runtime_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_state": { + "name": "runtime_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_sessions": { + "name": "active_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_sessions": { + "name": "total_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "runtime_updated_at": { + "name": "runtime_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_presence_agent_id_agents_uuid_fk": { + "name": "agent_presence_agent_id_agents_uuid_fk", + "tableFrom": "agent_presence", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_presence_client_id_clients_id_fk": { + "name": "agent_presence_client_id_clients_id_fk", + "tableFrom": "agent_presence", + "columnsFrom": [ + "client_id" + ], + "tableTo": "clients", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_resource_bindings": { + "name": "agent_resource_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replaces_resource_id": { + "name": "replaces_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inline_prompt_body": { + "name": "inline_prompt_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_local_path": { + "name": "repo_local_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_agent_resource_bindings_agent": { + "name": "idx_agent_resource_bindings_agent", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_agent_resource_bindings_resource": { + "name": "idx_agent_resource_bindings_resource", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_agent_resource_bindings_replaces": { + "name": "idx_agent_resource_bindings_replaces", + "columns": [ + { + "expression": "replaces_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "agent_resource_bindings_organization_id_organizations_id_fk": { + "name": "agent_resource_bindings_organization_id_organizations_id_fk", + "tableFrom": "agent_resource_bindings", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_resource_bindings_agent_id_agents_uuid_fk": { + "name": "agent_resource_bindings_agent_id_agents_uuid_fk", + "tableFrom": "agent_resource_bindings", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_resource_bindings_resource_id_resources_id_fk": { + "name": "agent_resource_bindings_resource_id_resources_id_fk", + "tableFrom": "agent_resource_bindings", + "columnsFrom": [ + "resource_id" + ], + "tableTo": "resources", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_resource_bindings_replaces_resource_id_resources_id_fk": { + "name": "agent_resource_bindings_replaces_resource_id_resources_id_fk", + "tableFrom": "agent_resource_bindings", + "columnsFrom": [ + "replaces_resource_id" + ], + "tableTo": "resources", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "uuid": { + "name": "uuid", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delegate_mention": { + "name": "delegate_mention", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_id": { + "name": "inbox_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "manager_id": { + "name": "manager_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_provider": { + "name": "runtime_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-code'" + }, + "avatar_color_token": { + "name": "avatar_color_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_image_data": { + "name": "avatar_image_data", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "avatar_image_mime": { + "name": "avatar_image_mime", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_image_updated_at": { + "name": "avatar_image_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "skills": { + "name": "skills", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_agents_org": { + "name": "idx_agents_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_agents_manager": { + "name": "idx_agents_manager", + "columns": [ + { + "expression": "manager_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_agents_visibility_org": { + "name": "idx_agents_visibility_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_agents_client": { + "name": "idx_agents_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "agents_organization_id_organizations_id_fk": { + "name": "agents_organization_id_organizations_id_fk", + "tableFrom": "agents", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "agents_client_id_clients_id_fk": { + "name": "agents_client_id_clients_id_fk", + "tableFrom": "agents", + "columnsFrom": [ + "client_id" + ], + "tableTo": "clients", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_inbox_id_unique": { + "name": "agents_inbox_id_unique", + "columns": [ + "inbox_id" + ], + "nullsNotDistinct": false + }, + "uq_agents_org_name": { + "name": "uq_agents_org_name", + "columns": [ + "organization_id", + "name" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "attachments_uploaded_by_idx": { + "name": "attachments_uploaded_by_idx", + "columns": [ + { + "expression": "uploaded_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "attachments_created_at_idx": { + "name": "attachments_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attentions": { + "name": "attentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin_chat_id": { + "name": "origin_chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_human_id": { + "name": "target_human_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "requires_response": { + "name": "requires_response", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "response": { + "name": "response", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responded_by": { + "name": "responded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responded_at": { + "name": "responded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled": { + "name": "cancelled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cancelled_reason": { + "name": "cancelled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_attentions_target_open": { + "name": "idx_attentions_target_open", + "columns": [ + { + "expression": "target_human_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_attentions_chat_open": { + "name": "idx_attentions_chat_open", + "columns": [ + { + "expression": "origin_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_attentions_origin": { + "name": "idx_attentions_origin", + "columns": [ + { + "expression": "origin_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_identities": { + "name": "auth_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "credential_type": { + "name": "credential_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_payload": { + "name": "credential_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_auth_identities_user": { + "name": "idx_auth_identities_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_auth_identities_email": { + "name": "idx_auth_identities_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "auth_identities_user_id_users_id_fk": { + "name": "auth_identities_user_id_users_id_fk", + "tableFrom": "auth_identities", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_auth_identities_provider_identifier": { + "name": "uq_auth_identities_provider_identifier", + "columns": [ + "provider", + "identifier" + ], + "nullsNotDistinct": false + }, + "uq_auth_identities_user_provider": { + "name": "uq_auth_identities_user_provider", + "columns": [ + "user_id", + "provider" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_membership": { + "name": "chat_membership", + "schema": "", + "columns": { + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "access_mode": { + "name": "access_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_membership_agent": { + "name": "idx_membership_agent", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_membership_chat_role": { + "name": "idx_membership_chat_role", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "access_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "chat_membership_chat_id_agent_id_pk": { + "name": "chat_membership_chat_id_agent_id_pk", + "columns": [ + "chat_id", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_user_state": { + "name": "chat_user_state", + "schema": "", + "columns": { + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "unread_mention_count": { + "name": "unread_mention_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "open_request_count": { + "name": "open_request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "engagement_status": { + "name": "engagement_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_user_state_agent": { + "name": "idx_user_state_agent", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_user_state_unread": { + "name": "idx_user_state_unread", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "unread_mention_count > 0", + "concurrently": false + }, + "idx_user_state_open_req": { + "name": "idx_user_state_open_req", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "open_request_count > 0", + "concurrently": false + }, + "idx_user_state_pinned": { + "name": "idx_user_state_pinned", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "pinned_at IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "chat_user_state_chat_id_agent_id_pk": { + "name": "chat_user_state_chat_id_agent_id_pk", + "columns": [ + "chat_id", + "agent_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chats": { + "name": "chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'direct'" + }, + "topic": { + "name": "topic", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description_updated_at": { + "name": "description_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "lifecycle_policy": { + "name": "lifecycle_policy", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'persistent'" + }, + "parent_chat_id": { + "name": "parent_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "onboarding_kickoff_key": { + "name": "onboarding_kickoff_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_message_preview": { + "name": "last_message_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activity_at": { + "name": "activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_chats_org_last_message": { + "name": "idx_chats_org_last_message", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"last_message_at\" desc", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_chats_org_activity": { + "name": "idx_chats_org_activity", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"activity_at\" desc", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "uq_chats_onboarding_kickoff_key": { + "name": "uq_chats_onboarding_kickoff_key", + "columns": [ + { + "expression": "onboarding_kickoff_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "chats_organization_id_organizations_id_fk": { + "name": "chats_organization_id_organizations_id_fk", + "tableFrom": "chats", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clients": { + "name": "clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'disconnected'" + }, + "sdk_version": { + "name": "sdk_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "os": { + "name": "os", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "paused_reason": { + "name": "paused_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_clients_user": { + "name": "idx_clients_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_clients_org": { + "name": "idx_clients_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "clients_user_id_users_id_fk": { + "name": "clients_user_id_users_id_fk", + "tableFrom": "clients", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "clients_organization_id_organizations_id_fk": { + "name": "clients_organization_id_organizations_id_fk", + "tableFrom": "clients", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ck_clients_paused_reason": { + "name": "ck_clients_paused_reason", + "value": "\"clients\".\"paused_reason\" IS NULL OR \"clients\".\"paused_reason\" IN ('auth_rejected', 'auth_refresh_failed')" + } + }, + "isRLSEnabled": false + }, + "public.connect_codes": { + "name": "connect_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_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": { + "idx_connect_codes_user": { + "name": "idx_connect_codes_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_connect_codes_expires_at": { + "name": "idx_connect_codes_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "connect_codes_user_id_users_id_fk": { + "name": "connect_codes_user_id_users_id_fk", + "tableFrom": "connect_codes", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connect_codes_code_hash_unique": { + "name": "connect_codes_code_hash_unique", + "columns": [ + "code_hash" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.context_tree_io_events": { + "name": "context_tree_io_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_session_event_id": { + "name": "source_session_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_index": { + "name": "source_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "runtime_provider": { + "name": "runtime_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tree_repo_url": { + "name": "tree_repo_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tree_branch": { + "name": "tree_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_path": { + "name": "target_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_context_tree_io_source": { + "name": "uq_context_tree_io_source", + "columns": [ + { + "expression": "source_session_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_context_tree_io_org_recent": { + "name": "idx_context_tree_io_org_recent", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_context_tree_io_org_action_recent": { + "name": "idx_context_tree_io_org_action_recent", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_context_tree_io_org_agent_recent": { + "name": "idx_context_tree_io_org_agent_recent", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_context_tree_io_org_target_recent": { + "name": "idx_context_tree_io_org_target_recent", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "context_tree_io_events_organization_id_organizations_id_fk": { + "name": "context_tree_io_events_organization_id_organizations_id_fk", + "tableFrom": "context_tree_io_events", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "context_tree_io_events_agent_id_agents_uuid_fk": { + "name": "context_tree_io_events_agent_id_agents_uuid_fk", + "tableFrom": "context_tree_io_events", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "context_tree_io_events_chat_id_chats_id_fk": { + "name": "context_tree_io_events_chat_id_chats_id_fk", + "tableFrom": "context_tree_io_events", + "columnsFrom": [ + "chat_id" + ], + "tableTo": "chats", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ck_context_tree_io_action": { + "name": "ck_context_tree_io_action", + "value": "\"context_tree_io_events\".\"action\" IN ('read', 'write')" + }, + "ck_context_tree_io_target_kind": { + "name": "ck_context_tree_io_target_kind", + "value": "\"context_tree_io_events\".\"target_kind\" IN ('file', 'directory', 'repo')" + }, + "ck_context_tree_io_target_path_nonempty": { + "name": "ck_context_tree_io_target_path_nonempty", + "value": "\"context_tree_io_events\".\"target_path\" <> ''" + } + }, + "isRLSEnabled": false + }, + "public.cron_jobs": { + "name": "cron_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "owner_member_id": { + "name": "owner_member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "control_chat_id": { + "name": "control_chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_mode": { + "name": "chat_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reuse_control_chat'" + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_reason": { + "name": "state_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_trigger_message_id": { + "name": "last_trigger_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_cron_jobs_due": { + "name": "idx_cron_jobs_due", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"cron_jobs\".\"state\" = 'active'", + "concurrently": false + }, + "idx_cron_jobs_control_created": { + "name": "idx_cron_jobs_control_created", + "columns": [ + { + "expression": "control_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_cron_jobs_owner_created": { + "name": "idx_cron_jobs_owner_created", + "columns": [ + { + "expression": "owner_member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "cron_jobs_owner_member_id_members_id_fk": { + "name": "cron_jobs_owner_member_id_members_id_fk", + "tableFrom": "cron_jobs", + "columnsFrom": [ + "owner_member_id" + ], + "tableTo": "members", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "cron_jobs_control_chat_id_chats_id_fk": { + "name": "cron_jobs_control_chat_id_chats_id_fk", + "tableFrom": "cron_jobs", + "columnsFrom": [ + "control_chat_id" + ], + "tableTo": "chats", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "cron_jobs_agent_id_agents_uuid_fk": { + "name": "cron_jobs_agent_id_agents_uuid_fk", + "tableFrom": "cron_jobs", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "cron_jobs_last_trigger_message_id_messages_id_fk": { + "name": "cron_jobs_last_trigger_message_id_messages_id_fk", + "tableFrom": "cron_jobs", + "columnsFrom": [ + "last_trigger_message_id" + ], + "tableTo": "messages", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_cron_jobs_control_agent_name": { + "name": "uq_cron_jobs_control_agent_name", + "columns": [ + "control_chat_id", + "agent_id", + "name" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "ck_cron_jobs_state": { + "name": "ck_cron_jobs_state", + "value": "\"cron_jobs\".\"state\" IN ('active', 'paused')" + }, + "ck_cron_jobs_chat_mode": { + "name": "ck_cron_jobs_chat_mode", + "value": "\"cron_jobs\".\"chat_mode\" = 'reuse_control_chat'" + }, + "ck_cron_jobs_revision_positive": { + "name": "ck_cron_jobs_revision_positive", + "value": "\"cron_jobs\".\"revision\" > 0" + }, + "ck_cron_jobs_active_shape": { + "name": "ck_cron_jobs_active_shape", + "value": "(\"cron_jobs\".\"state\" = 'active' AND \"cron_jobs\".\"next_run_at\" IS NOT NULL AND \"cron_jobs\".\"state_reason\" IS NULL) OR (\"cron_jobs\".\"state\" = 'paused' AND \"cron_jobs\".\"next_run_at\" IS NULL AND \"cron_jobs\".\"state_reason\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.doc_comments": { + "name": "doc_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_kind": { + "name": "author_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor": { + "name": "anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "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_comments_document_status_idx": { + "name": "doc_comments_document_status_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_comments_parent_idx": { + "name": "doc_comments_parent_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "doc_comments_document_id_doc_documents_id_fk": { + "name": "doc_comments_document_id_doc_documents_id_fk", + "tableFrom": "doc_comments", + "columnsFrom": [ + "document_id" + ], + "tableTo": "doc_documents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "doc_comments_parent_id_doc_comments_id_fk": { + "name": "doc_comments_parent_id_doc_comments_id_fk", + "tableFrom": "doc_comments", + "columnsFrom": [ + "parent_id" + ], + "tableTo": "doc_comments", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.doc_documents": { + "name": "doc_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project": { + "name": "project", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_kind": { + "name": "created_by_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_name": { + "name": "created_by_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_documents_org_slug_unique": { + "name": "doc_documents_org_slug_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_documents_org_updated_idx": { + "name": "doc_documents_org_updated_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "doc_documents_organization_id_organizations_id_fk": { + "name": "doc_documents_organization_id_organizations_id_fk", + "tableFrom": "doc_documents", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.doc_versions": { + "name": "doc_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "number": { + "name": "number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_kind": { + "name": "author_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_versions_document_number_unique": { + "name": "doc_versions_document_number_unique", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "doc_versions_document_id_doc_documents_id_fk": { + "name": "doc_versions_document_id_doc_documents_id_fk", + "tableFrom": "doc_versions", + "columnsFrom": [ + "document_id" + ], + "tableTo": "doc_documents", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_app_installations": { + "name": "github_app_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "account_type": { + "name": "account_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_login": { + "name": "account_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_github_id": { + "name": "account_github_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "installer_github_id": { + "name": "installer_github_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "requester_github_id": { + "name": "requester_github_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "hub_organization_id": { + "name": "hub_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "events": { + "name": "events", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "suspended_at": { + "name": "suspended_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": { + "uq_github_app_installations_installation_id": { + "name": "uq_github_app_installations_installation_id", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "uq_github_app_installations_hub_org": { + "name": "uq_github_app_installations_hub_org", + "columns": [ + { + "expression": "hub_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_github_app_installations_account": { + "name": "idx_github_app_installations_account", + "columns": [ + { + "expression": "account_github_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_github_app_installations_installer": { + "name": "idx_github_app_installations_installer", + "columns": [ + { + "expression": "installer_github_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_github_app_installations_requester": { + "name": "idx_github_app_installations_requester", + "columns": [ + { + "expression": "requester_github_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "github_app_installations_hub_organization_id_organizations_id_fk": { + "name": "github_app_installations_hub_organization_id_organizations_id_fk", + "tableFrom": "github_app_installations", + "columnsFrom": [ + "hub_organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ck_github_app_installations_account_type": { + "name": "ck_github_app_installations_account_type", + "value": "\"github_app_installations\".\"account_type\" IN ('User', 'Organization')" + } + }, + "isRLSEnabled": false + }, + "public.github_entity_chat_mappings": { + "name": "github_entity_chat_mappings", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "human_agent_id": { + "name": "human_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "delegate_agent_id": { + "name": "delegate_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_key": { + "name": "entity_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bound_at": { + "name": "bound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "bound_via": { + "name": "bound_via", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_state": { + "name": "entity_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "entity_state_updated_at": { + "name": "entity_state_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_github_entity_chat_mappings_chat": { + "name": "idx_github_entity_chat_mappings_chat", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_github_entity_chat_mappings_chat_state": { + "name": "idx_github_entity_chat_mappings_chat_state", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "github_entity_chat_mappings_organization_id_organizations_id_fk": { + "name": "github_entity_chat_mappings_organization_id_organizations_id_fk", + "tableFrom": "github_entity_chat_mappings", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "github_entity_chat_mappings_human_agent_id_agents_uuid_fk": { + "name": "github_entity_chat_mappings_human_agent_id_agents_uuid_fk", + "tableFrom": "github_entity_chat_mappings", + "columnsFrom": [ + "human_agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "github_entity_chat_mappings_delegate_agent_id_agents_uuid_fk": { + "name": "github_entity_chat_mappings_delegate_agent_id_agents_uuid_fk", + "tableFrom": "github_entity_chat_mappings", + "columnsFrom": [ + "delegate_agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "github_entity_chat_mappings_chat_id_chats_id_fk": { + "name": "github_entity_chat_mappings_chat_id_chats_id_fk", + "tableFrom": "github_entity_chat_mappings", + "columnsFrom": [ + "chat_id" + ], + "tableTo": "chats", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "github_entity_chat_mappings_organization_id_human_agent_id_delegate_agent_id_entity_type_entity_key_pk": { + "name": "github_entity_chat_mappings_organization_id_human_agent_id_delegate_agent_id_entity_type_entity_key_pk", + "columns": [ + "organization_id", + "human_agent_id", + "delegate_agent_id", + "entity_type", + "entity_key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.gitlab_connections": { + "name": "gitlab_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_origin": { + "name": "instance_origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint_first_seen_at": { + "name": "endpoint_first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_valid_inbound_at": { + "name": "last_valid_inbound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_system_hook_merge_request_inbound_at": { + "name": "last_system_hook_merge_request_inbound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_processing_failure_at": { + "name": "last_processing_failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_processing_failure_code": { + "name": "last_processing_failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stable_delivery_observed_at": { + "name": "stable_delivery_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_observed_version": { + "name": "last_observed_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reviewer_mode": { + "name": "reviewer_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "last_reviewer_schema_anomaly_at": { + "name": "last_reviewer_schema_anomaly_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_reviewer_schema_anomaly_code": { + "name": "last_reviewer_schema_anomaly_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_member_id": { + "name": "created_by_member_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_member_id": { + "name": "updated_by_member_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_gitlab_connections_org": { + "name": "uq_gitlab_connections_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "uq_gitlab_connections_token_hash": { + "name": "uq_gitlab_connections_token_hash", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "gitlab_connections_organization_id_organizations_id_fk": { + "name": "gitlab_connections_organization_id_organizations_id_fk", + "tableFrom": "gitlab_connections", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "gitlab_connections_created_by_member_id_members_id_fk": { + "name": "gitlab_connections_created_by_member_id_members_id_fk", + "tableFrom": "gitlab_connections", + "columnsFrom": [ + "created_by_member_id" + ], + "tableTo": "members", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + }, + "gitlab_connections_updated_by_member_id_members_id_fk": { + "name": "gitlab_connections_updated_by_member_id_members_id_fk", + "tableFrom": "gitlab_connections", + "columnsFrom": [ + "updated_by_member_id" + ], + "tableTo": "members", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ck_gitlab_connections_reviewer_mode": { + "name": "ck_gitlab_connections_reviewer_mode", + "value": "\"gitlab_connections\".\"reviewer_mode\" IN ('unknown', 'assignee', 'reviewers')" + } + }, + "isRLSEnabled": false + }, + "public.gitlab_entity_chat_mappings": { + "name": "gitlab_entity_chat_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "declared_by_agent_id": { + "name": "declared_by_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bound_via": { + "name": "bound_via", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent_declared'" + }, + "identity_link_id": { + "name": "identity_link_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "human_agent_id": { + "name": "human_agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delegate_agent_id": { + "name": "delegate_agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attention_mode": { + "name": "attention_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'legacy_route_only'" + }, + "attention_backfill_version": { + "name": "attention_backfill_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_iid": { + "name": "entity_iid", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "project_path": { + "name": "project_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_path_normalized": { + "name": "project_path_normalized", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_url": { + "name": "entity_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "entity_state": { + "name": "entity_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "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": { + "uq_gitlab_entity_pending_pair": { + "name": "uq_gitlab_entity_pending_pair", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "human_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delegate_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_path_normalized", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_iid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"gitlab_entity_chat_mappings\".\"project_id\" IS NULL AND \"gitlab_entity_chat_mappings\".\"active\" AND \"gitlab_entity_chat_mappings\".\"bound_via\" <> 'identity_target' AND \"gitlab_entity_chat_mappings\".\"human_agent_id\" IS NOT NULL AND \"gitlab_entity_chat_mappings\".\"delegate_agent_id\" IS NOT NULL", + "concurrently": false + }, + "uq_gitlab_entity_observed_pair": { + "name": "uq_gitlab_entity_observed_pair", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "human_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delegate_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_iid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"gitlab_entity_chat_mappings\".\"project_id\" IS NOT NULL AND \"gitlab_entity_chat_mappings\".\"active\" AND \"gitlab_entity_chat_mappings\".\"bound_via\" <> 'identity_target' AND \"gitlab_entity_chat_mappings\".\"human_agent_id\" IS NOT NULL AND \"gitlab_entity_chat_mappings\".\"delegate_agent_id\" IS NOT NULL", + "concurrently": false + }, + "uq_gitlab_entity_pending_legacy_chat": { + "name": "uq_gitlab_entity_pending_legacy_chat", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_path_normalized", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_iid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"gitlab_entity_chat_mappings\".\"project_id\" IS NULL AND \"gitlab_entity_chat_mappings\".\"active\" AND \"gitlab_entity_chat_mappings\".\"bound_via\" <> 'identity_target' AND \"gitlab_entity_chat_mappings\".\"human_agent_id\" IS NULL AND \"gitlab_entity_chat_mappings\".\"delegate_agent_id\" IS NULL", + "concurrently": false + }, + "uq_gitlab_entity_observed_legacy_chat": { + "name": "uq_gitlab_entity_observed_legacy_chat", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_iid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"gitlab_entity_chat_mappings\".\"project_id\" IS NOT NULL AND \"gitlab_entity_chat_mappings\".\"active\" AND \"gitlab_entity_chat_mappings\".\"bound_via\" <> 'identity_target' AND \"gitlab_entity_chat_mappings\".\"human_agent_id\" IS NULL AND \"gitlab_entity_chat_mappings\".\"delegate_agent_id\" IS NULL", + "concurrently": false + }, + "uq_gitlab_entity_identity_target": { + "name": "uq_gitlab_entity_identity_target", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "identity_link_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_iid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"gitlab_entity_chat_mappings\".\"project_id\" IS NOT NULL AND \"gitlab_entity_chat_mappings\".\"active\" AND \"gitlab_entity_chat_mappings\".\"bound_via\" = 'identity_target'", + "concurrently": false + }, + "idx_gitlab_entity_observed_lookup": { + "name": "idx_gitlab_entity_observed_lookup", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_iid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_gitlab_entity_pending_lookup": { + "name": "idx_gitlab_entity_pending_lookup", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_path_normalized", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_iid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"gitlab_entity_chat_mappings\".\"project_id\" IS NULL", + "concurrently": false + }, + "idx_gitlab_entity_chat": { + "name": "idx_gitlab_entity_chat", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "gitlab_entity_chat_mappings_organization_id_organizations_id_fk": { + "name": "gitlab_entity_chat_mappings_organization_id_organizations_id_fk", + "tableFrom": "gitlab_entity_chat_mappings", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "gitlab_entity_chat_mappings_connection_id_gitlab_connections_id_fk": { + "name": "gitlab_entity_chat_mappings_connection_id_gitlab_connections_id_fk", + "tableFrom": "gitlab_entity_chat_mappings", + "columnsFrom": [ + "connection_id" + ], + "tableTo": "gitlab_connections", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "gitlab_entity_chat_mappings_chat_id_chats_id_fk": { + "name": "gitlab_entity_chat_mappings_chat_id_chats_id_fk", + "tableFrom": "gitlab_entity_chat_mappings", + "columnsFrom": [ + "chat_id" + ], + "tableTo": "chats", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "gitlab_entity_chat_mappings_declared_by_agent_id_agents_uuid_fk": { + "name": "gitlab_entity_chat_mappings_declared_by_agent_id_agents_uuid_fk", + "tableFrom": "gitlab_entity_chat_mappings", + "columnsFrom": [ + "declared_by_agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "gitlab_entity_chat_mappings_identity_link_id_gitlab_identity_links_id_fk": { + "name": "gitlab_entity_chat_mappings_identity_link_id_gitlab_identity_links_id_fk", + "tableFrom": "gitlab_entity_chat_mappings", + "columnsFrom": [ + "identity_link_id" + ], + "tableTo": "gitlab_identity_links", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "gitlab_entity_chat_mappings_human_agent_id_agents_uuid_fk": { + "name": "gitlab_entity_chat_mappings_human_agent_id_agents_uuid_fk", + "tableFrom": "gitlab_entity_chat_mappings", + "columnsFrom": [ + "human_agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "gitlab_entity_chat_mappings_delegate_agent_id_agents_uuid_fk": { + "name": "gitlab_entity_chat_mappings_delegate_agent_id_agents_uuid_fk", + "tableFrom": "gitlab_entity_chat_mappings", + "columnsFrom": [ + "delegate_agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ck_gitlab_entity_type": { + "name": "ck_gitlab_entity_type", + "value": "\"gitlab_entity_chat_mappings\".\"entity_type\" IN ('issue', 'pull_request')" + }, + "ck_gitlab_entity_bound_via": { + "name": "ck_gitlab_entity_bound_via", + "value": "\"gitlab_entity_chat_mappings\".\"bound_via\" IN ('agent_declared', 'human_declared', 'identity_target')" + }, + "ck_gitlab_entity_identity_owner": { + "name": "ck_gitlab_entity_identity_owner", + "value": "\"gitlab_entity_chat_mappings\".\"bound_via\" <> 'identity_target' OR (\"gitlab_entity_chat_mappings\".\"identity_link_id\" IS NOT NULL AND \"gitlab_entity_chat_mappings\".\"human_agent_id\" IS NOT NULL AND \"gitlab_entity_chat_mappings\".\"delegate_agent_id\" IS NOT NULL AND \"gitlab_entity_chat_mappings\".\"project_id\" IS NOT NULL)" + }, + "ck_gitlab_entity_attention_pair": { + "name": "ck_gitlab_entity_attention_pair", + "value": "(\"gitlab_entity_chat_mappings\".\"human_agent_id\" IS NULL AND \"gitlab_entity_chat_mappings\".\"delegate_agent_id\" IS NULL) OR (\"gitlab_entity_chat_mappings\".\"human_agent_id\" IS NOT NULL AND \"gitlab_entity_chat_mappings\".\"delegate_agent_id\" IS NOT NULL)" + }, + "ck_gitlab_entity_attention_mode": { + "name": "ck_gitlab_entity_attention_mode", + "value": "\"gitlab_entity_chat_mappings\".\"attention_mode\" IN ('paired', 'legacy_route_only')" + } + }, + "isRLSEnabled": false + }, + "public.gitlab_identity_links": { + "name": "gitlab_identity_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "membership_id": { + "name": "membership_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_username": { + "name": "normalized_username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_gitlab_identity_connection_membership": { + "name": "uq_gitlab_identity_connection_membership", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "uq_gitlab_identity_connection_username": { + "name": "uq_gitlab_identity_connection_username", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "normalized_username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_gitlab_identity_org_state": { + "name": "idx_gitlab_identity_org_state", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_gitlab_identity_membership_state": { + "name": "idx_gitlab_identity_membership_state", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "gitlab_identity_links_organization_id_organizations_id_fk": { + "name": "gitlab_identity_links_organization_id_organizations_id_fk", + "tableFrom": "gitlab_identity_links", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "gitlab_identity_links_membership_id_members_id_fk": { + "name": "gitlab_identity_links_membership_id_members_id_fk", + "tableFrom": "gitlab_identity_links", + "columnsFrom": [ + "membership_id" + ], + "tableTo": "members", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "gitlab_identity_links_connection_id_gitlab_connections_id_fk": { + "name": "gitlab_identity_links_connection_id_gitlab_connections_id_fk", + "tableFrom": "gitlab_identity_links", + "columnsFrom": [ + "connection_id" + ], + "tableTo": "gitlab_connections", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ck_gitlab_identity_state": { + "name": "ck_gitlab_identity_state", + "value": "\"gitlab_identity_links\".\"state\" IN ('active', 'suspended')" + } + }, + "isRLSEnabled": false + }, + "public.inbox_entries": { + "name": "inbox_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "inbox_id": { + "name": "inbox_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "notify": { + "name": "notify", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acked_at": { + "name": "acked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_inbox_pending": { + "name": "idx_inbox_pending", + "columns": [ + { + "expression": "inbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_inbox_pending_notify": { + "name": "idx_inbox_pending_notify", + "columns": [ + { + "expression": "inbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "status = 'pending' AND notify = true", + "concurrently": false + }, + "idx_inbox_chat_silent": { + "name": "idx_inbox_chat_silent", + "columns": [ + { + "expression": "inbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "notify", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_inbox_entries_message_status": { + "name": "idx_inbox_entries_message_status", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "inbox_entries_message_id_messages_id_fk": { + "name": "inbox_entries_message_id_messages_id_fk", + "tableFrom": "inbox_entries", + "columnsFrom": [ + "message_id" + ], + "tableTo": "messages", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_inbox_delivery": { + "name": "uq_inbox_delivery", + "columns": [ + "inbox_id", + "message_id", + "chat_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "ck_inbox_entries_status": { + "name": "ck_inbox_entries_status", + "value": "\"inbox_entries\".\"status\" IN ('pending', 'delivered', 'acked')" + } + }, + "isRLSEnabled": false + }, + "public.invitation_redemptions": { + "name": "invitation_redemptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_invitation_redemptions_invitation": { + "name": "idx_invitation_redemptions_invitation", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_invitation_redemptions_user": { + "name": "idx_invitation_redemptions_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "invitation_redemptions_invitation_id_invitations_id_fk": { + "name": "invitation_redemptions_invitation_id_invitations_id_fk", + "tableFrom": "invitation_redemptions", + "columnsFrom": [ + "invitation_id" + ], + "tableTo": "invitations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "invitation_redemptions_user_id_users_id_fk": { + "name": "invitation_redemptions_user_id_users_id_fk", + "tableFrom": "invitation_redemptions", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitations": { + "name": "invitations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_invitations_token": { + "name": "idx_invitations_token", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_invitations_org": { + "name": "idx_invitations_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "invitations_organization_id_organizations_id_fk": { + "name": "invitations_organization_id_organizations_id_fk", + "tableFrom": "invitations", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "invitations_created_by_users_id_fk": { + "name": "invitations_created_by_users_id_fk", + "tableFrom": "invitations", + "columnsFrom": [ + "created_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitations_token_unique": { + "name": "invitations_token_unique", + "columns": [ + "token" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.members": { + "name": "members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "onboarding_suppressed_at": { + "name": "onboarding_suppressed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "onboarding_suppressed_reason": { + "name": "onboarding_suppressed_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "onboarding_completed_at": { + "name": "onboarding_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_members_user": { + "name": "idx_members_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_members_org": { + "name": "idx_members_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "members_user_id_users_id_fk": { + "name": "members_user_id_users_id_fk", + "tableFrom": "members", + "columnsFrom": [ + "user_id" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "members_organization_id_organizations_id_fk": { + "name": "members_organization_id_organizations_id_fk", + "tableFrom": "members", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + }, + "members_agent_id_agents_uuid_fk": { + "name": "members_agent_id_agents_uuid_fk", + "tableFrom": "members", + "columnsFrom": [ + "agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "members_agent_id_unique": { + "name": "members_agent_id_unique", + "columns": [ + "agent_id" + ], + "nullsNotDistinct": false + }, + "uq_members_user_org": { + "name": "uq_members_user_org", + "columns": [ + "user_id", + "organization_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "ck_members_completed_implies_suppressed": { + "name": "ck_members_completed_implies_suppressed", + "value": "\"members\".\"onboarding_completed_at\" IS NULL OR \"members\".\"onboarding_suppressed_at\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sender_id": { + "name": "sender_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "reply_to_inbox": { + "name": "reply_to_inbox", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reply_to_chat": { + "name": "reply_to_chat", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_messages_chat_time": { + "name": "idx_messages_chat_time", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_messages_in_reply_to": { + "name": "idx_messages_in_reply_to", + "columns": [ + { + "expression": "in_reply_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_messages_chat_source_time": { + "name": "idx_messages_chat_source_time", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_messages_mentions": { + "name": "idx_messages_mentions", + "columns": [ + { + "expression": "((\"metadata\" -> 'mentions')) jsonb_path_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "foreignKeys": { + "messages_chat_id_chats_id_fk": { + "name": "messages_chat_id_chats_id_fk", + "tableFrom": "messages", + "columnsFrom": [ + "chat_id" + ], + "tableTo": "chats", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "read": { + "name": "read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "dedup_key": { + "name": "dedup_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_notifications_org_created": { + "name": "idx_notifications_org_created", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_notifications_agent": { + "name": "idx_notifications_agent", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_notifications_org_read": { + "name": "idx_notifications_org_read", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "uq_notifications_org_dedup_unread": { + "name": "uq_notifications_org_dedup_unread", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dedup_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "read = false AND dedup_key IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_settings": { + "name": "organization_settings", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_org_settings_namespace": { + "name": "idx_org_settings_namespace", + "columns": [ + { + "expression": "namespace", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "organization_settings_organization_id_organizations_id_fk": { + "name": "organization_settings_organization_id_organizations_id_fk", + "tableFrom": "organization_settings", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_settings_updated_by_users_id_fk": { + "name": "organization_settings_updated_by_users_id_fk", + "tableFrom": "organization_settings", + "columnsFrom": [ + "updated_by" + ], + "tableTo": "users", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": { + "organization_settings_organization_id_namespace_pk": { + "name": "organization_settings_organization_id_namespace_pk", + "columns": [ + "organization_id", + "namespace" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "max_agents": { + "name": "max_agents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_messages_per_minute": { + "name": "max_messages_per_minute", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "features": { + "name": "features", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_name_unique": { + "name": "organizations_name_unique", + "columns": [ + "name" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_questions": { + "name": "pending_questions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "answered_at": { + "name": "answered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_reason": { + "name": "superseded_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_pending_questions_agent_status": { + "name": "idx_pending_questions_agent_status", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_pending_questions_chat_status": { + "name": "idx_pending_questions_chat_status", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resources": { + "name": "resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_canonical_key": { + "name": "repo_canonical_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_enabled": { + "name": "default_enabled", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_resources_org_type_scope": { + "name": "idx_resources_org_type_scope", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_resources_owner_agent": { + "name": "idx_resources_owner_agent", + "columns": [ + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_resources_repo_key": { + "name": "idx_resources_repo_key", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_canonical_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "uq_resources_team_repo_canonical_active": { + "name": "uq_resources_team_repo_canonical_active", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_canonical_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"resources\".\"type\" = 'repo' AND \"resources\".\"scope\" = 'team' AND \"resources\".\"status\" IN ('active', 'stale') AND \"resources\".\"repo_canonical_key\" IS NOT NULL", + "concurrently": false + }, + "uq_resources_agent_repo_canonical_active": { + "name": "uq_resources_agent_repo_canonical_active", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_canonical_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"resources\".\"type\" = 'repo' AND \"resources\".\"scope\" = 'agent' AND \"resources\".\"status\" IN ('active', 'stale') AND \"resources\".\"repo_canonical_key\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "resources_organization_id_organizations_id_fk": { + "name": "resources_organization_id_organizations_id_fk", + "tableFrom": "resources", + "columnsFrom": [ + "organization_id" + ], + "tableTo": "organizations", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "resources_owner_agent_id_agents_uuid_fk": { + "name": "resources_owner_agent_id_agents_uuid_fk", + "tableFrom": "resources", + "columnsFrom": [ + "owner_agent_id" + ], + "tableTo": "agents", + "columnsTo": [ + "uuid" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.server_instances": { + "name": "server_instances", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "last_heartbeat": { + "name": "last_heartbeat", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_events": { + "name": "session_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_session_events_chat_seq": { + "name": "uq_session_events_chat_seq", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_session_events_chat_created": { + "name": "idx_session_events_chat_created", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_session_events_context_tree_usage_recent": { + "name": "idx_session_events_context_tree_usage_recent", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"session_events\".\"kind\" = 'context_tree_usage'", + "concurrently": false + }, + "idx_session_events_context_tree_io_agent_recent": { + "name": "idx_session_events_context_tree_io_agent_recent", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"session_events\".\"kind\" IN ('context_tree_usage', 'tool_call')", + "concurrently": false + }, + "idx_session_events_token_usage_agent_recent": { + "name": "idx_session_events_token_usage_agent_recent", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"session_events\".\"kind\" = 'token_usage'", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "columns": [ + "username" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/server/drizzle/meta/_journal.json b/packages/server/drizzle/meta/_journal.json index 84b8bc89b..467fa4d03 100644 --- a/packages/server/drizzle/meta/_journal.json +++ b/packages/server/drizzle/meta/_journal.json @@ -589,6 +589,13 @@ "when": 1784805906735, "tag": "0083_tiresome_bucky", "breakpoints": true + }, + { + "idx": 84, + "version": "7", + "when": 1784845617269, + "tag": "0084_processed_events_claim_lifecycle", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/server/src/__tests__/background-tasks-extra.test.ts b/packages/server/src/__tests__/background-tasks-extra.test.ts index 728f38f1e..40d79c478 100644 --- a/packages/server/src/__tests__/background-tasks-extra.test.ts +++ b/packages/server/src/__tests__/background-tasks-extra.test.ts @@ -15,12 +15,14 @@ const serviceMocks = { notifyAgentEvent: vi.fn(), pruneStaleSilentEntries: vi.fn(), sweepChatArchive: vi.fn(), + sweepExpiredEventClaims: vi.fn(), }; const mockedModules = [ "../observability/index.js", "../services/chat-archive.js", "../services/client.js", + "../services/event-dedup.js", "../services/inbox.js", "../services/notification.js", "../services/presence.js", @@ -36,6 +38,9 @@ function mockBackgroundTaskDependencies(): void { vi.doMock("../services/client.js", () => ({ cleanupStaleClients: serviceMocks.cleanupStaleClients, })); + vi.doMock("../services/event-dedup.js", () => ({ + sweepExpiredEventClaims: serviceMocks.sweepExpiredEventClaims, + })); vi.doMock("../services/inbox.js", () => ({ pruneStaleSilentEntries: serviceMocks.pruneStaleSilentEntries, })); @@ -78,6 +83,7 @@ beforeEach(() => { serviceMocks.notifyAgentEvent.mockResolvedValue(undefined); serviceMocks.pruneStaleSilentEntries.mockResolvedValue({ ackedDeleted: 0, stalePendingDeleted: 0 }); serviceMocks.sweepChatArchive.mockResolvedValue({ mappedRowsArchived: 0, unmappedRowsArchived: 0 }); + serviceMocks.sweepExpiredEventClaims.mockResolvedValue(0); }); afterEach(() => { @@ -94,6 +100,7 @@ describe("createBackgroundTasks", () => { serviceMocks.pruneStaleSilentEntries.mockResolvedValue({ ackedDeleted: 2, stalePendingDeleted: 1 }); serviceMocks.markStaleAgents.mockResolvedValue(["agent_1", "agent_2"]); serviceMocks.sweepChatArchive.mockResolvedValue({ mappedRowsArchived: 1, unmappedRowsArchived: 1 }); + serviceMocks.sweepExpiredEventClaims.mockResolvedValue(3); const app = makeApp(30); const tasks = createBackgroundTasks(app, "srv_1"); @@ -120,6 +127,8 @@ describe("createBackgroundTasks", () => { { mappedRowsArchived: 1, unmappedRowsArchived: 1 }, "chat auto-archive sweep flipped rows to archived", ); + expect(serviceMocks.sweepExpiredEventClaims).toHaveBeenCalledWith(app.db); + expect(loggerMocks.info).toHaveBeenCalledWith({ swept: 3 }, "swept expired pending webhook event claims"); await tasks.stop(); expect(vi.getTimerCount()).toBe(0); @@ -133,6 +142,7 @@ describe("createBackgroundTasks", () => { .mockResolvedValueOnce(undefined) .mockRejectedValueOnce(new Error("heartbeat failed")); serviceMocks.notifyAgentEvent.mockRejectedValueOnce(new Error("notify failed")); + serviceMocks.sweepExpiredEventClaims.mockRejectedValueOnce(new Error("claim sweep failed")); const app = makeApp(0); const tasks = createBackgroundTasks(app, "srv_2"); @@ -146,6 +156,10 @@ describe("createBackgroundTasks", () => { { err: expect.any(Error) }, "failed to heartbeat / cleanup presence", ); + expect(loggerMocks.error).toHaveBeenCalledWith( + { err: expect.any(Error) }, + "failed to sweep expired webhook event claims", + ); await tasks.stop(); }); diff --git a/packages/server/src/__tests__/event-dedup.test.ts b/packages/server/src/__tests__/event-dedup.test.ts index c766e275a..3f6161358 100644 --- a/packages/server/src/__tests__/event-dedup.test.ts +++ b/packages/server/src/__tests__/event-dedup.test.ts @@ -1,17 +1,33 @@ +import { sql } from "drizzle-orm"; import { describe, expect, it } from "vitest"; +import type { Database } from "../db/connection.js"; import * as eventDedup from "../services/event-dedup.js"; import { useTestApp } from "./helpers.js"; +async function readClaim( + db: Database, + eventId: string, + platform: string, +): Promise<{ status: string; expires_at: string | Date | null } | null> { + const rows = await db.execute<{ status: string; expires_at: string | Date | null }>( + sql`SELECT status, expires_at FROM processed_events WHERE event_id = ${eventId} AND platform = ${platform}`, + ); + return rows[0] ?? null; +} + describe("Event deduplication", () => { const getApp = useTestApp(); - it("claims an event the first time", async () => { + it("claims an event the first time as a pending claim with an expiry", async () => { const app = getApp(); const claimed = await eventDedup.claimEvent(app.db, "evt_unique_1", "github"); expect(claimed).toBe(true); + const row = await readClaim(app.db, "evt_unique_1", "github"); + expect(row?.status).toBe("pending"); + expect(row?.expires_at).not.toBeNull(); }); - it("rejects a duplicate event", async () => { + it("rejects a duplicate while an unexpired pending claim is in flight", async () => { const app = getApp(); await eventDedup.claimEvent(app.db, "evt_dup_1", "github"); const duplicate = await eventDedup.claimEvent(app.db, "evt_dup_1", "github"); @@ -33,4 +49,84 @@ describe("Event deduplication", () => { const reclaimed = await eventDedup.claimEvent(app.db, "evt_unclaim_1", "github"); expect(reclaimed).toBe(true); }); + + it("completeEvent flips the claim to done and dedupes permanently", async () => { + const app = getApp(); + await eventDedup.claimEvent(app.db, "evt_done_1", "github"); + const completed = await eventDedup.completeEvent(app.db, "evt_done_1", "github"); + expect(completed).toBe(true); + const row = await readClaim(app.db, "evt_done_1", "github"); + expect(row?.status).toBe("done"); + expect(row?.expires_at).toBeNull(); + // A done claim is never taken over, no matter how long ago it landed. + expect(await eventDedup.claimEvent(app.db, "evt_done_1", "github")).toBe(false); + expect(await eventDedup.claimEvent(app.db, "evt_done_1", "github", 0)).toBe(false); + }); + + it("completeEvent returns false when no pending claim exists", async () => { + const app = getApp(); + expect(await eventDedup.completeEvent(app.db, "evt_missing_1", "github")).toBe(false); + await eventDedup.claimEvent(app.db, "evt_done_twice_1", "github"); + expect(await eventDedup.completeEvent(app.db, "evt_done_twice_1", "github")).toBe(true); + expect(await eventDedup.completeEvent(app.db, "evt_done_twice_1", "github")).toBe(false); + }); + + it("takes over an expired pending claim (crashed processor) and renews it", async () => { + const app = getApp(); + // TTL 0 → the claim is expired the moment it lands, like a claim whose + // processor crashed before completing or unclaiming and whose TTL passed. + expect(await eventDedup.claimEvent(app.db, "evt_expired_1", "github", 0)).toBe(true); + const retaken = await eventDedup.claimEvent(app.db, "evt_expired_1", "github"); + expect(retaken).toBe(true); + const row = await readClaim(app.db, "evt_expired_1", "github"); + expect(row?.status).toBe("pending"); + // The takeover renewed the expiry, so a third delivery is deduped again. + expect(await eventDedup.claimEvent(app.db, "evt_expired_1", "github")).toBe(false); + }); + + it("unclaimEvent never deletes a done claim", async () => { + const app = getApp(); + await eventDedup.claimEvent(app.db, "evt_keep_done_1", "github"); + await eventDedup.completeEvent(app.db, "evt_keep_done_1", "github"); + await eventDedup.unclaimEvent(app.db, "evt_keep_done_1", "github"); + const row = await readClaim(app.db, "evt_keep_done_1", "github"); + expect(row?.status).toBe("done"); + expect(await eventDedup.claimEvent(app.db, "evt_keep_done_1", "github")).toBe(false); + }); + + it("sweeps only expired pending claims", async () => { + const app = getApp(); + await eventDedup.claimEvent(app.db, "evt_sweep_expired_1", "github", 0); + await eventDedup.claimEvent(app.db, "evt_sweep_inflight_1", "github"); + await eventDedup.claimEvent(app.db, "evt_sweep_done_1", "github"); + await eventDedup.completeEvent(app.db, "evt_sweep_done_1", "github"); + + const swept = await eventDedup.sweepExpiredEventClaims(app.db); + + expect(swept).toBeGreaterThanOrEqual(1); + expect(await readClaim(app.db, "evt_sweep_expired_1", "github")).toBeNull(); + expect((await readClaim(app.db, "evt_sweep_inflight_1", "github"))?.status).toBe("pending"); + expect((await readClaim(app.db, "evt_sweep_done_1", "github"))?.status).toBe("done"); + // The swept id is claimable again; the in-flight and done ids are not. + expect(await eventDedup.claimEvent(app.db, "evt_sweep_expired_1", "github")).toBe(true); + expect(await eventDedup.claimEvent(app.db, "evt_sweep_inflight_1", "github")).toBe(false); + expect(await eventDedup.claimEvent(app.db, "evt_sweep_done_1", "github")).toBe(false); + }); + + it("grants exactly one claim to concurrent deliveries of a fresh id", async () => { + const app = getApp(); + const results = await Promise.all( + Array.from({ length: 8 }, () => eventDedup.claimEvent(app.db, "evt_race_fresh_1", "github")), + ); + expect(results.filter(Boolean)).toHaveLength(1); + }); + + it("grants exactly one takeover to concurrent deliveries of an expired pending id", async () => { + const app = getApp(); + await eventDedup.claimEvent(app.db, "evt_race_expired_1", "github", 0); + const results = await Promise.all( + Array.from({ length: 8 }, () => eventDedup.claimEvent(app.db, "evt_race_expired_1", "github")), + ); + expect(results.filter(Boolean)).toHaveLength(1); + }); }); diff --git a/packages/server/src/__tests__/github-app-webhook-route.test.ts b/packages/server/src/__tests__/github-app-webhook-route.test.ts index 8be9c69b8..86f9088d0 100644 --- a/packages/server/src/__tests__/github-app-webhook-route.test.ts +++ b/packages/server/src/__tests__/github-app-webhook-route.test.ts @@ -1882,6 +1882,40 @@ describe("POST /webhooks/github-app", () => { expect(second.json().deduped).toBe(true); }); + it("redelivery after a crashed processor's claim expires is processed, not deduped", async () => { + const app = getApp(); + const admin = await createTestAdmin(app); + const installationId = 100024; + await seedInstallation(app, { installationId, orgId: admin.organizationId }); + const deliveryId = randomUUID(); + // Simulate the issue #317 scenario: a previous processor claimed this + // delivery and crashed before finishing (no done mark, no unclaim). The + // pending claim is committed and past its TTL when GitHub redelivers. + expect(await eventDedupService.claimEvent(app.db, deliveryId, "github", 0)).toBe(true); + + const payload = { + action: "opened", + issue: { + number: 6, + title: "redelivered", + html_url: "https://github.com/owner/repo/issues/6", + body: "", + assignees: [], + }, + repository: { full_name: "owner/repo" }, + sender: { login: "external", type: "User" }, + installation: { id: installationId }, + }; + + const redelivered = await postWebhook(app, "issues", payload, { deliveryId }); + expect(redelivered.statusCode).toBe(200); + expect(redelivered.json().deduped).toBeUndefined(); + // The redelivery completed, so a further duplicate is deduped for good. + const third = await postWebhook(app, "issues", payload, { deliveryId }); + expect(third.statusCode).toBe(200); + expect(third.json().deduped).toBe(true); + }); + it("duplicate context reviewer delivery is deduped and does not send another task", async () => { const app = getApp(); const admin = await createTestAdmin(app); diff --git a/packages/server/src/__tests__/scm-webhook-processing.test.ts b/packages/server/src/__tests__/scm-webhook-processing.test.ts index af2db2f6c..a292f02fc 100644 --- a/packages/server/src/__tests__/scm-webhook-processing.test.ts +++ b/packages/server/src/__tests__/scm-webhook-processing.test.ts @@ -1,5 +1,7 @@ import type { NormalizedScmEvent, ScmIngressContext } from "@first-tree/shared"; +import { sql } from "drizzle-orm"; import { describe, expect, it, vi } from "vitest"; +import { claimEvent } from "../services/event-dedup.js"; import { processScmWebhookDelivery } from "../services/scm-webhook-processing.js"; import { useTestApp } from "./helpers.js"; @@ -215,6 +217,104 @@ describe("processScmWebhookDelivery", () => { expect(duplicate).toEqual({ outcome: "duplicate" }); }); + it("marks a successful stable delivery done so it dedupes permanently", async () => { + const app = getApp(); + const ingress = makeIngress("delivery-done"); + const event = makeEvent(ingress); + const input = { + db: app.db, + ingress, + observation: null, + event, + applyObservation: async () => undefined, + runProviderWork: async () => null, + resolveAudience: async () => ({ targets: ["target-1"], actorHumanId: null }), + deliver: async () => ({ delivered: 1, failed: 0 }), + }; + + expect((await processScmWebhookDelivery(input)).outcome).toBe("delivered"); + + const rows = await app.db.execute<{ status: string; expires_at: string | Date | null }>( + sql`SELECT status, expires_at FROM processed_events WHERE event_id = ${"delivery-done"} AND platform = ${"github"}`, + ); + expect(rows[0]?.status).toBe("done"); + expect(rows[0]?.expires_at).toBeNull(); + expect((await processScmWebhookDelivery(input)).outcome).toBe("duplicate"); + }); + + it("reprocesses a delivery whose previous pending claim expired (crash injection)", async () => { + const app = getApp(); + const ingress = makeIngress("delivery-crashed"); + const event = makeEvent(ingress); + // Simulate a processor that claimed the delivery and then died before + // completing or unclaiming: the pending claim is committed and, by the + // time the provider redelivers, past its TTL (TTL 0 here). + expect(await claimEvent(app.db, "delivery-crashed", "github", 0)).toBe(true); + + const deliver = vi.fn(async () => ({ delivered: 1, failed: 0 })); + const input = { + db: app.db, + ingress, + observation: null, + event, + applyObservation: async () => undefined, + runProviderWork: async () => null, + resolveAudience: async () => ({ targets: ["target-1"], actorHumanId: null }), + deliver, + }; + + const redelivered = await processScmWebhookDelivery(input); + expect(redelivered.outcome).toBe("delivered"); + expect(deliver).toHaveBeenCalledOnce(); + // The redelivery completed, so the event now dedupes permanently. + expect((await processScmWebhookDelivery(input)).outcome).toBe("duplicate"); + }); + + it("dedupes against an unexpired in-flight pending claim", async () => { + const app = getApp(); + const ingress = makeIngress("delivery-inflight"); + const event = makeEvent(ingress); + // Another processor holds a live pending claim right now. + expect(await claimEvent(app.db, "delivery-inflight", "github")).toBe(true); + + const deliver = vi.fn(async () => ({ delivered: 1, failed: 0 })); + const result = await processScmWebhookDelivery({ + db: app.db, + ingress, + observation: null, + event, + applyObservation: async () => undefined, + runProviderWork: async () => null, + resolveAudience: async () => ({ targets: ["target-1"], actorHumanId: null }), + deliver, + }); + expect(result).toEqual({ outcome: "duplicate" }); + expect(deliver).not.toHaveBeenCalled(); + }); + + it("processes concurrent deliveries of the same stable id exactly once", async () => { + const app = getApp(); + const ingress = makeIngress("delivery-race"); + const event = makeEvent(ingress); + const deliver = vi.fn(async () => ({ delivered: 1, failed: 0 })); + const makeInput = () => ({ + db: app.db, + ingress, + observation: null, + event, + applyObservation: async () => undefined, + runProviderWork: async () => null, + resolveAudience: async () => ({ targets: ["target-1"], actorHumanId: null }), + deliver, + }); + + const results = await Promise.all(Array.from({ length: 4 }, () => processScmWebhookDelivery(makeInput()))); + + expect(results.filter((r) => r.outcome === "delivered")).toHaveLength(1); + expect(results.filter((r) => r.outcome === "duplicate")).toHaveLength(3); + expect(deliver).toHaveBeenCalledOnce(); + }); + it("rejects a normalized event whose provider-neutral ingress facts drift", async () => { const app = getApp(); const ingress = makeIngress("delivery-mismatch"); diff --git a/packages/server/src/db/schema/processed-events.ts b/packages/server/src/db/schema/processed-events.ts index 381dcbb96..2dce45c55 100644 --- a/packages/server/src/db/schema/processed-events.ts +++ b/packages/server/src/db/schema/processed-events.ts @@ -1,9 +1,25 @@ -import { pgTable, serial, text, timestamp, unique } from "drizzle-orm/pg-core"; +import { index, pgTable, serial, text, timestamp, unique } from "drizzle-orm/pg-core"; /** - * Webhook event deduplication. External webhook sources (e.g. GitHub) can - * deliver the same event multiple times; we use INSERT ... ON CONFLICT DO - * NOTHING to ensure single processing. + * Webhook event deduplication with a claim lifecycle. + * + * External webhook sources (e.g. GitHub) can deliver the same event multiple + * times. A delivery is claimed with `status = 'pending'` plus an expiry before + * any side effects run, and flipped to `status = 'done'` once the handler + * completes. Redeliveries are deduped against `done` claims (and unexpired + * in-flight `pending` claims); an expired `pending` claim is taken over + * atomically, so a crash between claim and completion can never lose the + * event forever. + * + * The column default is `'done'` so rows that predate the lifecycle keep + * deduping after the migration backfill. Live code always sets `status` + * explicitly. + * + * NOTE: this table predates drizzle-kit management (created by the + * hand-written 0003_feishu_adapter.sql) and is deliberately NOT exported + * from schema/index.ts — a regular `db:generate` would emit a conflicting + * CREATE TABLE. Schema changes ship as `drizzle-kit generate --custom` + * migrations (see 0084); keep this file in sync as documentation parity. */ export const processedEvents = pgTable( "processed_events", @@ -11,7 +27,13 @@ export const processedEvents = pgTable( id: serial("id").primaryKey(), eventId: text("event_id").notNull(), platform: text("platform").notNull(), + status: text("status").notNull().default("done"), + /** Expiry for `pending` claims; `null` once the claim is `done`. */ + expiresAt: timestamp("expires_at", { withTimezone: true }), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), }, - (table) => [unique("uq_processed_event").on(table.eventId, table.platform)], + (table) => [ + unique("uq_processed_event").on(table.eventId, table.platform), + index("idx_processed_events_status_expires").on(table.status, table.expiresAt), + ], ); diff --git a/packages/server/src/services/background-tasks.ts b/packages/server/src/services/background-tasks.ts index 76a649e0a..23d903255 100644 --- a/packages/server/src/services/background-tasks.ts +++ b/packages/server/src/services/background-tasks.ts @@ -3,6 +3,7 @@ import { createLogger } from "../observability/index.js"; import * as chatArchiveService from "./chat-archive.js"; import * as clientService from "./client.js"; import { createCronScheduler } from "./cron-scheduler.js"; +import * as eventDedupService from "./event-dedup.js"; import * as inboxService from "./inbox.js"; import * as notificationService from "./notification.js"; import * as presenceService from "./presence.js"; @@ -18,6 +19,7 @@ export function createBackgroundTasks(app: FastifyInstance, instanceId: string): let inboxTimer: ReturnType | null = null; let heartbeatTimer: ReturnType | null = null; let archiveSweepTimer: ReturnType | null = null; + let eventClaimSweepTimer: ReturnType | null = null; const cronScheduler = createCronScheduler(app); return { @@ -36,6 +38,20 @@ export function createBackgroundTasks(app: FastifyInstance, instanceId: string): } }, 60_000); + // Expired webhook claim sweep. Redelivery correctness does not depend + // on it (claimEvent takes expired pending claims over in place); it + // keeps the table from accumulating claims left by crashed processors. + eventClaimSweepTimer = setInterval(async () => { + try { + const swept = await eventDedupService.sweepExpiredEventClaims(app.db); + if (swept > 0) { + log.info({ swept }, "swept expired pending webhook event claims"); + } + } catch (err) { + log.error({ err }, "failed to sweep expired webhook event claims"); + } + }, 60_000); + heartbeatTimer = setInterval(async () => { try { await presenceService.heartbeatInstance(app.db, instanceId); @@ -91,6 +107,10 @@ export function createBackgroundTasks(app: FastifyInstance, instanceId: string): clearInterval(archiveSweepTimer); archiveSweepTimer = null; } + if (eventClaimSweepTimer) { + clearInterval(eventClaimSweepTimer); + eventClaimSweepTimer = null; + } }, }; } diff --git a/packages/server/src/services/event-dedup.ts b/packages/server/src/services/event-dedup.ts index 557e197c3..037741698 100644 --- a/packages/server/src/services/event-dedup.ts +++ b/packages/server/src/services/event-dedup.ts @@ -2,22 +2,95 @@ import { sql } from "drizzle-orm"; import type { Database } from "../db/connection.js"; // ── Event deduplication ───────────────────────────────────────────── +// +// Claim lifecycle (see db/schema/processed-events.ts): +// +// claimEvent → INSERT status='pending' with an expiry, or atomically +// take over an expired 'pending' row. Fails against 'done' +// rows and unexpired in-flight 'pending' rows. +// completeEvent → flip 'pending' → 'done' after the handler succeeded. +// unclaimEvent → best-effort delete of the 'pending' row on handler +// failure so the provider's immediate retry can clear. +// Correctness does NOT depend on it: if it never runs, +// the 'pending' claim expires and a redelivery takes it +// over via claimEvent. +// sweepExpiredEventClaims → background cleanup of expired 'pending' +// rows so the table does not accumulate stuck claims. + +/** How long a `pending` claim blocks other deliveries of the same event. */ +export const EVENT_CLAIM_TTL_SECONDS = 300; /** * Attempt to claim an event for processing. - * Returns true if this is the first time the event is seen, false if duplicate. + * + * Returns true when this delivery should be processed: either the event was + * never seen, or a previous claim is still `pending` past its expiry (the + * previous processor crashed or stalled) and this delivery takes it over. + * Returns false for duplicates: the event is `done`, or another processor + * holds an unexpired `pending` claim right now. + * + * Atomicity: `INSERT ... ON CONFLICT DO UPDATE` locks the conflicting row, + * so two concurrent deliveries of the same id serialize and exactly one + * gets the claim. + */ +export async function claimEvent( + db: Database, + eventId: string, + platform: string, + ttlSeconds: number = EVENT_CLAIM_TTL_SECONDS, +): Promise { + const result = await db.execute<{ event_id: string }>( + sql`INSERT INTO processed_events (event_id, platform, status, expires_at) + VALUES (${eventId}, ${platform}, 'pending', now() + make_interval(secs => ${ttlSeconds})) + ON CONFLICT (event_id, platform) DO UPDATE + SET status = 'pending', + expires_at = now() + make_interval(secs => ${ttlSeconds}), + created_at = now() + WHERE processed_events.status = 'pending' AND processed_events.expires_at <= now() + RETURNING event_id`, + ); + return result.length > 0; +} + +/** + * Mark a claimed event as successfully processed so future redeliveries of + * the same id are deduped permanently. Returns false when no `pending` claim + * existed (e.g. it expired and was swept or taken over mid-processing). */ -export async function claimEvent(db: Database, eventId: string, platform: string): Promise { +export async function completeEvent(db: Database, eventId: string, platform: string): Promise { const result = await db.execute<{ event_id: string }>( - sql`INSERT INTO processed_events (event_id, platform) VALUES (${eventId}, ${platform}) ON CONFLICT DO NOTHING RETURNING event_id`, + sql`UPDATE processed_events + SET status = 'done', expires_at = NULL + WHERE event_id = ${eventId} AND platform = ${platform} AND status = 'pending' + RETURNING event_id`, ); return result.length > 0; } /** - * Remove a claimed event so it can be retried on next delivery. - * Called when processing fails after claimEvent() succeeded. + * Remove a `pending` claim so the event can be retried on the provider's + * next delivery without waiting for the claim to expire. Called when + * processing fails after claimEvent() succeeded. Best-effort optimization + * only — an untouched `pending` claim expires on its own. */ export async function unclaimEvent(db: Database, eventId: string, platform: string): Promise { - await db.execute(sql`DELETE FROM processed_events WHERE event_id = ${eventId} AND platform = ${platform}`); + await db.execute( + sql`DELETE FROM processed_events + WHERE event_id = ${eventId} AND platform = ${platform} AND status = 'pending'`, + ); +} + +/** + * Delete expired `pending` claims left behind by crashed processors so the + * table does not accumulate stuck rows. Redelivery correctness does not + * depend on the sweep (claimEvent takes expired claims over in place), but + * the sweep keeps the table bounded. Returns the number of rows removed. + */ +export async function sweepExpiredEventClaims(db: Database): Promise { + const result = await db.execute<{ event_id: string }>( + sql`DELETE FROM processed_events + WHERE status = 'pending' AND expires_at <= now() + RETURNING event_id`, + ); + return result.length; } diff --git a/packages/server/src/services/scm-webhook-processing.ts b/packages/server/src/services/scm-webhook-processing.ts index fac5a035c..4ada12f50 100644 --- a/packages/server/src/services/scm-webhook-processing.ts +++ b/packages/server/src/services/scm-webhook-processing.ts @@ -1,7 +1,7 @@ import type { NormalizedScmEvent, ScmEntityObservation, ScmIngressContext } from "@first-tree/shared"; import type { Database } from "../db/connection.js"; import { createLogger } from "../observability/index.js"; -import { claimEvent, unclaimEvent } from "./event-dedup.js"; +import { claimEvent, completeEvent, unclaimEvent } from "./event-dedup.js"; const log = createLogger("ScmWebhookProcessing"); @@ -50,10 +50,16 @@ type ProcessScmWebhookDeliveryInput = * Narrow provider-neutral SCM processing seam. * * The ingress adapter authenticates and normalizes first. This kernel owns - * only the existing optional whole-request claim, provider work covered by - * that claim, audience orchestration, and best-effort unclaim on an uncaught - * top-level failure. Provider payloads, stores, mapping rules, card shapes, - * and per-chat failure guards remain behind the supplied callbacks. + * only the optional whole-request claim lifecycle (pending claim before the + * work, done mark after it, best-effort unclaim on an uncaught top-level + * failure), provider work covered by that claim, and audience orchestration. + * Provider payloads, stores, mapping rules, card shapes, and per-chat + * failure guards remain behind the supplied callbacks. + * + * Claim semantics: the pending claim carries a TTL, so a crash that skips + * both the done mark and the unclaim leaves a claim that expires instead of + * deduping the event forever — a later redelivery takes the expired claim + * over and reprocesses. */ export async function processScmWebhookDelivery( input: ProcessScmWebhookDeliveryInput, @@ -69,54 +75,14 @@ export async function processScmWebhookDelivery; try { - const providerResult = await input.runProviderWork(); - let observationOutcome: ScmObservationOutcome = "not_applicable"; - if (input.observation) { - try { - await input.applyObservation(input.observation); - observationOutcome = "applied"; - } catch (err) { - observationOutcome = "failed"; - // Projection refresh is deliberately independent from notification - // delivery. A temporary projection failure must not suppress an - // otherwise valid card or make a provider retry duplicate it. - log.error( - { - err, - provider: input.ingress.provider, - organizationId: input.ingress.source.organizationId, - entityType: input.observation?.entity.type, - entityKey: input.observation?.entity.key, - }, - "failed to apply SCM entity observation", - ); - } - } - if (!input.event) return { outcome: "provider_only", observationOutcome, providerResult }; - - const audience = await input.resolveAudience(input.event); - if (audience.targets.length === 0) { - const reason = input.event.targets.length > 0 ? "audience_empty_with_targets" : "audience_empty_no_targets"; - log.info( - { - provider: input.event.provider, - organizationId: input.event.source.organizationId, - entityType: input.event.entity.type, - entityKey: input.event.entity.key, - actor: input.event.actor.externalUsername, - targetsCount: input.event.targets.length, - reason, - }, - "SCM webhook audience empty, skipping", - ); - return { outcome: "audience_empty", reason, observationOutcome, providerResult }; - } - - const deliveryStats = await input.deliver(input.event, audience); - return { outcome: "delivered", deliveryStats, observationOutcome, providerResult }; + result = await runClaimedWork(input); } catch (err) { if (deliveryId) { + // Best-effort optimization so the provider's immediate retry can + // clear without waiting out the claim TTL. Correctness does not + // depend on this delete: an untouched pending claim expires. await unclaimEvent(input.db, deliveryId, input.ingress.provider).catch((unclaimErr) => { log.error( { err: unclaimErr, provider: input.ingress.provider, deliveryId }, @@ -126,6 +92,77 @@ export async function processScmWebhookDelivery( + input: ProcessScmWebhookDeliveryInput, +): Promise> { + const providerResult = await input.runProviderWork(); + let observationOutcome: ScmObservationOutcome = "not_applicable"; + if (input.observation) { + try { + await input.applyObservation(input.observation); + observationOutcome = "applied"; + } catch (err) { + observationOutcome = "failed"; + // Projection refresh is deliberately independent from notification + // delivery. A temporary projection failure must not suppress an + // otherwise valid card or make a provider retry duplicate it. + log.error( + { + err, + provider: input.ingress.provider, + organizationId: input.ingress.source.organizationId, + entityType: input.observation?.entity.type, + entityKey: input.observation?.entity.key, + }, + "failed to apply SCM entity observation", + ); + } + } + if (!input.event) return { outcome: "provider_only", observationOutcome, providerResult }; + + const audience = await input.resolveAudience(input.event); + if (audience.targets.length === 0) { + const reason = input.event.targets.length > 0 ? "audience_empty_with_targets" : "audience_empty_no_targets"; + log.info( + { + provider: input.event.provider, + organizationId: input.event.source.organizationId, + entityType: input.event.entity.type, + entityKey: input.event.entity.key, + actor: input.event.actor.externalUsername, + targetsCount: input.event.targets.length, + reason, + }, + "SCM webhook audience empty, skipping", + ); + return { outcome: "audience_empty", reason, observationOutcome, providerResult }; + } + + const deliveryStats = await input.deliver(input.event, audience); + return { outcome: "delivered", deliveryStats, observationOutcome, providerResult }; } function assertEventMatchesIngress(ingress: ScmIngressContext, event: NormalizedScmEvent | null): void {