diff --git a/packages/server/drizzle/0084_sloppy_masque.sql b/packages/server/drizzle/0084_sloppy_masque.sql new file mode 100644 index 000000000..3fee405cd --- /dev/null +++ b/packages/server/drizzle/0084_sloppy_masque.sql @@ -0,0 +1,54 @@ +-- Replace the chat-silent composite with two partial indexes keyed on `id`. +-- +-- Preceding-context assembly now runs as one set-based statement instead of +-- one query per claimed trigger. Inside the LATERAL, each trigger's window +-- bounds (`> previous notify cursor`, `< trigger`) are correlated values, so +-- the planner cannot estimate their width. Without `id` in the index it scans +-- every silent row in the chat and discards almost all of them. +-- +-- idx_inbox_chat_silent_pending — the silent rows a trigger bundles as +-- preceding context, and ACK-through's silent drain. +-- +-- idx_inbox_chat_notify — the previous-trigger cursor and ACK-through's +-- contiguous notify prefix. Deliberately not filtered on `status`: an +-- already-acked trigger still closes a preceding-context window, and the +-- prefix check has to walk acked rows to prove there is no gap. +-- +-- Partial rather than appending `status`/`notify` to the key: a key ending in +-- the unique `id` defeats B-tree deduplication. Measured on 240k rows, the old +-- four-column form costs ~7 bytes per row because near-duplicate keys compress; +-- appending `id` makes every key unique and costs ~49 bytes per row (1.7 MB -> +-- 11 MB). Restricting the row set keeps the pair at ~2.6 MB, and since +-- `inbox_entries` is append-only, excluding consumed rows matters more as the +-- table grows. +-- +-- Operator note: +-- Drizzle migrator executes migration files inside a transaction, so this file +-- cannot use CREATE INDEX CONCURRENTLY. A plain CREATE INDEX takes a SHARE lock +-- and DROP INDEX takes ACCESS EXCLUSIVE; on a large `inbox_entries` — which +-- sits in the message delivery hot path — that is a visible write stall. For a +-- large production table, run the concurrent forms manually outside a +-- transaction before applying migrations, creating the new indexes BEFORE +-- dropping the old one so no lookup is ever left unindexed: +-- +-- 1. CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_inbox_chat_silent_pending" +-- ON "inbox_entries" ("inbox_id", "chat_id", "id") +-- WHERE status = 'pending' AND notify = false; +-- 2. CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_inbox_chat_notify" +-- ON "inbox_entries" ("inbox_id", "chat_id", "id") +-- WHERE notify = true; +-- 3. Verify both are valid (indisvalid in pg_index) and being used, then: +-- DROP INDEX CONCURRENTLY IF EXISTS "idx_inbox_chat_silent"; +-- 4. Re-run `pnpm db:migrate`. The IF NOT EXISTS / IF EXISTS clauses below +-- detect the manual work and skip it. +-- +-- The new indexes carry different names from the one being dropped precisely so +-- this ordering is possible; reusing `idx_inbox_chat_silent` would force a +-- window with no index backing the silent-row lookup. +CREATE INDEX IF NOT EXISTS "idx_inbox_chat_silent_pending" + ON "inbox_entries" USING btree ("inbox_id","chat_id","id") + WHERE status = 'pending' AND notify = false;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "idx_inbox_chat_notify" + ON "inbox_entries" USING btree ("inbox_id","chat_id","id") + WHERE notify = true;--> statement-breakpoint +DROP INDEX IF EXISTS "idx_inbox_chat_silent"; diff --git a/packages/server/drizzle/LATEST b/packages/server/drizzle/LATEST index 0a280305d..9de1c5c20 100644 --- a/packages/server/drizzle/LATEST +++ b/packages/server/drizzle/LATEST @@ -1 +1 @@ -0083_tiresome_bucky +0084_sloppy_masque diff --git a/packages/server/drizzle/meta/0084_snapshot.json b/packages/server/drizzle/meta/0084_snapshot.json new file mode 100644 index 000000000..55162bf91 --- /dev/null +++ b/packages/server/drizzle/meta/0084_snapshot.json @@ -0,0 +1,5873 @@ +{ + "id": "6a4962ef-db07-4d53-b005-8460238614b0", + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_chat_sessions_agent_id_agents_uuid_fk": { + "name": "agent_chat_sessions_agent_id_agents_uuid_fk", + "tableFrom": "agent_chat_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_chat_sessions_chat_id_chats_id_fk": { + "name": "agent_chat_sessions_chat_id_chats_id_fk", + "tableFrom": "agent_chat_sessions", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_presence_client_id_clients_id_fk": { + "name": "agent_presence_client_id_clients_id_fk", + "tableFrom": "agent_presence", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_agent_resource_bindings_resource": { + "name": "idx_agent_resource_bindings_resource", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_agent_resource_bindings_replaces": { + "name": "idx_agent_resource_bindings_replaces", + "columns": [ + { + "expression": "replaces_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_resource_bindings_organization_id_organizations_id_fk": { + "name": "agent_resource_bindings_organization_id_organizations_id_fk", + "tableFrom": "agent_resource_bindings", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_resource_bindings_agent_id_agents_uuid_fk": { + "name": "agent_resource_bindings_agent_id_agents_uuid_fk", + "tableFrom": "agent_resource_bindings", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_resource_bindings_resource_id_resources_id_fk": { + "name": "agent_resource_bindings_resource_id_resources_id_fk", + "tableFrom": "agent_resource_bindings", + "tableTo": "resources", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_resource_bindings_replaces_resource_id_resources_id_fk": { + "name": "agent_resource_bindings_replaces_resource_id_resources_id_fk", + "tableFrom": "agent_resource_bindings", + "tableTo": "resources", + "columnsFrom": [ + "replaces_resource_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_agents_manager": { + "name": "idx_agents_manager", + "columns": [ + { + "expression": "manager_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_agents_client": { + "name": "idx_agents_client", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_organization_id_organizations_id_fk": { + "name": "agents_organization_id_organizations_id_fk", + "tableFrom": "agents", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_client_id_clients_id_fk": { + "name": "agents_client_id_clients_id_fk", + "tableFrom": "agents", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "agents_inbox_id_unique": { + "name": "agents_inbox_id_unique", + "nullsNotDistinct": false, + "columns": [ + "inbox_id" + ] + }, + "uq_agents_org_name": { + "name": "uq_agents_org_name", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "name" + ] + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "attachments_created_at_idx": { + "name": "attachments_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_auth_identities_email": { + "name": "idx_auth_identities_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_identities_user_id_users_id_fk": { + "name": "auth_identities_user_id_users_id_fk", + "tableFrom": "auth_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_auth_identities_provider_identifier": { + "name": "uq_auth_identities_provider_identifier", + "nullsNotDistinct": false, + "columns": [ + "provider", + "identifier" + ] + }, + "uq_auth_identities_user_provider": { + "name": "uq_auth_identities_user_provider", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "provider" + ] + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_state_unread": { + "name": "idx_user_state_unread", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "unread_mention_count > 0", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_state_open_req": { + "name": "idx_user_state_open_req", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "open_request_count > 0", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_user_state_pinned": { + "name": "idx_user_state_pinned", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "pinned_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chats_org_activity": { + "name": "idx_chats_org_activity", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"activity_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uq_chats_onboarding_kickoff_key": { + "name": "uq_chats_onboarding_kickoff_key", + "columns": [ + { + "expression": "onboarding_kickoff_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chats_organization_id_organizations_id_fk": { + "name": "chats_organization_id_organizations_id_fk", + "tableFrom": "chats", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_clients_org": { + "name": "idx_clients_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "clients_user_id_users_id_fk": { + "name": "clients_user_id_users_id_fk", + "tableFrom": "clients", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "clients_organization_id_organizations_id_fk": { + "name": "clients_organization_id_organizations_id_fk", + "tableFrom": "clients", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_connect_codes_expires_at": { + "name": "idx_connect_codes_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connect_codes_user_id_users_id_fk": { + "name": "connect_codes_user_id_users_id_fk", + "tableFrom": "connect_codes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connect_codes_code_hash_unique": { + "name": "connect_codes_code_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "code_hash" + ] + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "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", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "no action", + "onUpdate": "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", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "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, + "where": "\"cron_jobs\".\"state\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cron_jobs_owner_member_id_members_id_fk": { + "name": "cron_jobs_owner_member_id_members_id_fk", + "tableFrom": "cron_jobs", + "tableTo": "members", + "columnsFrom": [ + "owner_member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cron_jobs_control_chat_id_chats_id_fk": { + "name": "cron_jobs_control_chat_id_chats_id_fk", + "tableFrom": "cron_jobs", + "tableTo": "chats", + "columnsFrom": [ + "control_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cron_jobs_agent_id_agents_uuid_fk": { + "name": "cron_jobs_agent_id_agents_uuid_fk", + "tableFrom": "cron_jobs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cron_jobs_last_trigger_message_id_messages_id_fk": { + "name": "cron_jobs_last_trigger_message_id_messages_id_fk", + "tableFrom": "cron_jobs", + "tableTo": "messages", + "columnsFrom": [ + "last_trigger_message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_cron_jobs_control_agent_name": { + "name": "uq_cron_jobs_control_agent_name", + "nullsNotDistinct": false, + "columns": [ + "control_chat_id", + "agent_id", + "name" + ] + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_comments_parent_idx": { + "name": "doc_comments_parent_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "doc_comments_document_id_doc_documents_id_fk": { + "name": "doc_comments_document_id_doc_documents_id_fk", + "tableFrom": "doc_comments", + "tableTo": "doc_documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "doc_comments_parent_id_doc_comments_id_fk": { + "name": "doc_comments_parent_id_doc_comments_id_fk", + "tableFrom": "doc_comments", + "tableTo": "doc_comments", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "doc_documents_organization_id_organizations_id_fk": { + "name": "doc_documents_organization_id_organizations_id_fk", + "tableFrom": "doc_documents", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "doc_versions_document_id_doc_documents_id_fk": { + "name": "doc_versions_document_id_doc_documents_id_fk", + "tableFrom": "doc_versions", + "tableTo": "doc_documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_github_app_installations_account": { + "name": "idx_github_app_installations_account", + "columns": [ + { + "expression": "account_github_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_github_app_installations_installer": { + "name": "idx_github_app_installations_installer", + "columns": [ + { + "expression": "installer_github_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_github_app_installations_requester": { + "name": "idx_github_app_installations_requester", + "columns": [ + { + "expression": "requester_github_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_app_installations_hub_organization_id_organizations_id_fk": { + "name": "github_app_installations_hub_organization_id_organizations_id_fk", + "tableFrom": "github_app_installations", + "tableTo": "organizations", + "columnsFrom": [ + "hub_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "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", + "tableTo": "agents", + "columnsFrom": [ + "human_agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "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", + "tableTo": "agents", + "columnsFrom": [ + "delegate_agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_entity_chat_mappings_chat_id_chats_id_fk": { + "name": "github_entity_chat_mappings_chat_id_chats_id_fk", + "tableFrom": "github_entity_chat_mappings", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uq_gitlab_connections_token_hash": { + "name": "uq_gitlab_connections_token_hash", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gitlab_connections_organization_id_organizations_id_fk": { + "name": "gitlab_connections_organization_id_organizations_id_fk", + "tableFrom": "gitlab_connections", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gitlab_connections_created_by_member_id_members_id_fk": { + "name": "gitlab_connections_created_by_member_id_members_id_fk", + "tableFrom": "gitlab_connections", + "tableTo": "members", + "columnsFrom": [ + "created_by_member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "gitlab_connections_updated_by_member_id_members_id_fk": { + "name": "gitlab_connections_updated_by_member_id_members_id_fk", + "tableFrom": "gitlab_connections", + "tableTo": "members", + "columnsFrom": [ + "updated_by_member_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "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, + "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, + "method": "btree", + "with": {} + }, + "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, + "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, + "method": "btree", + "with": {} + }, + "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, + "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, + "method": "btree", + "with": {} + }, + "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, + "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, + "method": "btree", + "with": {} + }, + "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, + "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, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "where": "\"gitlab_entity_chat_mappings\".\"project_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_gitlab_entity_chat": { + "name": "idx_gitlab_entity_chat", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "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", + "tableTo": "gitlab_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gitlab_entity_chat_mappings_chat_id_chats_id_fk": { + "name": "gitlab_entity_chat_mappings_chat_id_chats_id_fk", + "tableFrom": "gitlab_entity_chat_mappings", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "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", + "tableTo": "agents", + "columnsFrom": [ + "declared_by_agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "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", + "tableTo": "gitlab_identity_links", + "columnsFrom": [ + "identity_link_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "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", + "tableTo": "agents", + "columnsFrom": [ + "human_agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "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", + "tableTo": "agents", + "columnsFrom": [ + "delegate_agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "gitlab_identity_links_organization_id_organizations_id_fk": { + "name": "gitlab_identity_links_organization_id_organizations_id_fk", + "tableFrom": "gitlab_identity_links", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "gitlab_identity_links_membership_id_members_id_fk": { + "name": "gitlab_identity_links_membership_id_members_id_fk", + "tableFrom": "gitlab_identity_links", + "tableTo": "members", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "gitlab_identity_links_connection_id_gitlab_connections_id_fk": { + "name": "gitlab_identity_links_connection_id_gitlab_connections_id_fk", + "tableFrom": "gitlab_identity_links", + "tableTo": "gitlab_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "where": "status = 'pending' AND notify = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_inbox_chat_silent_pending": { + "name": "idx_inbox_chat_silent_pending", + "columns": [ + { + "expression": "inbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'pending' AND notify = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_inbox_chat_notify": { + "name": "idx_inbox_chat_notify", + "columns": [ + { + "expression": "inbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "notify = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inbox_entries_message_id_messages_id_fk": { + "name": "inbox_entries_message_id_messages_id_fk", + "tableFrom": "inbox_entries", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_inbox_delivery": { + "name": "uq_inbox_delivery", + "nullsNotDistinct": false, + "columns": [ + "inbox_id", + "message_id", + "chat_id" + ] + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invitation_redemptions_user": { + "name": "idx_invitation_redemptions_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_redemptions_invitation_id_invitations_id_fk": { + "name": "invitation_redemptions_invitation_id_invitations_id_fk", + "tableFrom": "invitation_redemptions", + "tableTo": "invitations", + "columnsFrom": [ + "invitation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_redemptions_user_id_users_id_fk": { + "name": "invitation_redemptions_user_id_users_id_fk", + "tableFrom": "invitation_redemptions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_invitations_org": { + "name": "idx_invitations_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitations_organization_id_organizations_id_fk": { + "name": "invitations_organization_id_organizations_id_fk", + "tableFrom": "invitations", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitations_created_by_users_id_fk": { + "name": "invitations_created_by_users_id_fk", + "tableFrom": "invitations", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitations_token_unique": { + "name": "invitations_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_members_org": { + "name": "idx_members_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "members_user_id_users_id_fk": { + "name": "members_user_id_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "members_organization_id_organizations_id_fk": { + "name": "members_organization_id_organizations_id_fk", + "tableFrom": "members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "members_agent_id_agents_uuid_fk": { + "name": "members_agent_id_agents_uuid_fk", + "tableFrom": "members", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "members_agent_id_unique": { + "name": "members_agent_id_unique", + "nullsNotDistinct": false, + "columns": [ + "agent_id" + ] + }, + "uq_members_user_org": { + "name": "uq_members_user_org", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "organization_id" + ] + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_in_reply_to": { + "name": "idx_messages_in_reply_to", + "columns": [ + { + "expression": "in_reply_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_mentions": { + "name": "idx_messages_mentions", + "columns": [ + { + "expression": "((\"metadata\" -> 'mentions')) jsonb_path_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "messages_chat_id_chats_id_fk": { + "name": "messages_chat_id_chats_id_fk", + "tableFrom": "messages", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notifications_agent": { + "name": "idx_notifications_agent", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "where": "read = false AND dedup_key IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_settings_organization_id_organizations_id_fk": { + "name": "organization_settings_organization_id_organizations_id_fk", + "tableFrom": "organization_settings", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_settings_updated_by_users_id_fk": { + "name": "organization_settings_updated_by_users_id_fk", + "tableFrom": "organization_settings", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "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", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_resources_owner_agent": { + "name": "idx_resources_owner_agent", + "columns": [ + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "where": "\"resources\".\"type\" = 'repo' AND \"resources\".\"scope\" = 'team' AND \"resources\".\"status\" IN ('active', 'stale') AND \"resources\".\"repo_canonical_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "where": "\"resources\".\"type\" = 'repo' AND \"resources\".\"scope\" = 'agent' AND \"resources\".\"status\" IN ('active', 'stale') AND \"resources\".\"repo_canonical_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resources_organization_id_organizations_id_fk": { + "name": "resources_organization_id_organizations_id_fk", + "tableFrom": "resources", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "resources_owner_agent_id_agents_uuid_fk": { + "name": "resources_owner_agent_id_agents_uuid_fk", + "tableFrom": "resources", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "uuid" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "where": "\"session_events\".\"kind\" = 'context_tree_usage'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "where": "\"session_events\".\"kind\" IN ('context_tree_usage', 'tool_call')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "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, + "where": "\"session_events\".\"kind\" = 'token_usage'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_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..7d5b05562 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": 1785259021331, + "tag": "0084_sloppy_masque", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/server/src/__tests__/inbox-delivery-indexes.test.ts b/packages/server/src/__tests__/inbox-delivery-indexes.test.ts index 3828658e3..aacab2ca3 100644 --- a/packages/server/src/__tests__/inbox-delivery-indexes.test.ts +++ b/packages/server/src/__tests__/inbox-delivery-indexes.test.ts @@ -28,6 +28,38 @@ describe("inbox delivery indexes", () => { expect(rows[0]?.indexdef).toContain("USING btree (message_id, status)"); }); + it("keeps the preceding-context indexes partial and keyed on entry id", async () => { + const rows = await getDb().execute<{ indexname: string; indexdef: string }>(sql` + SELECT indexname, indexdef + FROM pg_indexes + WHERE schemaname = 'public' + AND tablename = 'inbox_entries' + AND indexname IN ('idx_inbox_chat_silent_pending', 'idx_inbox_chat_notify') + ORDER BY indexname + `); + + expect(rows).toHaveLength(2); + const byName = new Map(rows.map((row) => [row.indexname, row.indexdef])); + + // `id` must stay in the key: the preceding-context window bounds are + // correlated values inside a LATERAL, so without it the planner scans + // every silent row in the chat. + const silent = byName.get("idx_inbox_chat_silent_pending"); + expect(silent).toContain("(inbox_id, chat_id, id)"); + // Partial, not a wider composite — a key ending in the unique `id` + // defeats B-tree deduplication and inflates the index several-fold. + expect(silent).toContain("WHERE"); + expect(silent).toContain("notify = false"); + + // The notify cursor must NOT be filtered on status: an already-acked + // trigger still closes a preceding-context window, and ACK-through walks + // acked rows to prove its prefix has no gap. + const notify = byName.get("idx_inbox_chat_notify"); + expect(notify).toContain("(inbox_id, chat_id, id)"); + expect(notify).toContain("WHERE (notify"); + expect(notify).not.toContain("status"); + }); + it("constrains inbox entry status to active delivery states", async () => { const rows = await getDb().execute<{ definition: string }>(sql` SELECT pg_get_constraintdef(oid) AS definition diff --git a/packages/server/src/__tests__/inbox-preceding-context-batch.test.ts b/packages/server/src/__tests__/inbox-preceding-context-batch.test.ts new file mode 100644 index 000000000..eb37640ae --- /dev/null +++ b/packages/server/src/__tests__/inbox-preceding-context-batch.test.ts @@ -0,0 +1,321 @@ +import { and, asc, eq } from "drizzle-orm"; +import type { FastifyInstance } from "fastify"; +import { describe, expect, it } from "vitest"; +import { inboxEntries } from "../db/schema/inbox-entries.js"; +import { messages } from "../db/schema/messages.js"; +import { createChat } from "../services/chat.js"; +import * as inboxService from "../services/inbox.js"; +import { sendMessage } from "../services/message.js"; +import { createTestAgent, useTestApp } from "./helpers.js"; + +/** + * Preceding-context assembly is built from one set-based statement rather than + * one query per claimed trigger. The invariants below are the ones a batched + * implementation can silently break while single-trigger tests stay green: + * per-trigger window boundaries inside a single drain, chat partitioning, + * and the ordering contract. + */ +describe("batched preceding-context assembly", () => { + const getApp = useTestApp(); + + async function seedChatWithObserver(app: FastifyInstance, uid: string) { + const human = await createTestAgent(app, { type: "human", name: `pcb-h-${uid}` }); + const observer = await createTestAgent(app, { type: "agent", name: `pcb-obs-${uid}` }); + const chat = await createChat(app.db, human.agent.uuid, { + type: "group", + participantIds: [observer.agent.uuid], + }); + return { human, observer, chat }; + } + + /** Message nobody is addressed in — lands as a silent (notify=false) row. */ + async function sendSilent(app: FastifyInstance, chatId: string, senderId: string, content: string) { + return sendMessage( + app.db, + chatId, + senderId, + { source: "api", format: "text", content }, + { allowRecipientlessSend: true }, + ); + } + + /** Message that @mentions the observer — lands as a notify=true trigger. */ + async function sendTrigger( + app: FastifyInstance, + chatId: string, + senderId: string, + observerId: string, + content: string, + ) { + return sendMessage(app.db, chatId, senderId, { + source: "api", + format: "text", + content, + metadata: { mentions: [observerId] }, + }); + } + + function precedingContents(delivery: { message: { precedingMessages: Array<{ content: unknown }> } }): unknown[] { + return delivery.message.precedingMessages.map((p) => p.content); + } + + it("gives each trigger in one drain its own window instead of a shared lower bound", async () => { + const app = getApp(); + const uid = crypto.randomUUID().slice(0, 6); + const { human, observer, chat } = await seedChatWithObserver(app, uid); + + // silent-a | T1 | silent-b | T2 | silent-c | T3 — all claimed in one drain. + await sendSilent(app, chat.id, human.agent.uuid, "silent-a"); + await sendTrigger(app, chat.id, human.agent.uuid, observer.agent.uuid, "trigger-1"); + await sendSilent(app, chat.id, human.agent.uuid, "silent-b"); + await sendTrigger(app, chat.id, human.agent.uuid, observer.agent.uuid, "trigger-2"); + await sendSilent(app, chat.id, human.agent.uuid, "silent-c"); + await sendTrigger(app, chat.id, human.agent.uuid, observer.agent.uuid, "trigger-3"); + + const claimed = await inboxService.claimBacklogForPush(app.db, observer.agent.inboxId, 10); + expect(claimed.map((entry) => entry.message.content)).toEqual(["trigger-1", "trigger-2", "trigger-3"]); + + // A shared lower bound would leak earlier silent rows into later triggers. + expect(claimed.map(precedingContents)).toEqual([["silent-a"], ["silent-b"], ["silent-c"]]); + }); + + it("keeps per-chat windows separate when one drain spans several chats", async () => { + const app = getApp(); + const uid = crypto.randomUUID().slice(0, 6); + const human = await createTestAgent(app, { type: "human", name: `pcb-mh-${uid}` }); + const observer = await createTestAgent(app, { type: "agent", name: `pcb-mobs-${uid}` }); + const chatOne = await createChat(app.db, human.agent.uuid, { + type: "group", + participantIds: [observer.agent.uuid], + }); + const chatTwo = await createChat(app.db, human.agent.uuid, { + type: "group", + participantIds: [observer.agent.uuid], + }); + + await sendSilent(app, chatOne.id, human.agent.uuid, "one-silent"); + await sendSilent(app, chatTwo.id, human.agent.uuid, "two-silent"); + await sendTrigger(app, chatOne.id, human.agent.uuid, observer.agent.uuid, "one-trigger"); + await sendTrigger(app, chatTwo.id, human.agent.uuid, observer.agent.uuid, "two-trigger"); + + const claimed = await inboxService.claimBacklogForPush(app.db, observer.agent.inboxId, 10); + const byContent = new Map(claimed.map((entry) => [entry.message.content, entry])); + + const one = byContent.get("one-trigger"); + const two = byContent.get("two-trigger"); + if (!one || !two) throw new Error("expected both chat triggers in one drain"); + // Chat two's silent row sits between chat one's silent row and its + // trigger by inbox id, so an unpartitioned window would capture it. + expect(precedingContents(one)).toEqual(["one-silent"]); + expect(precedingContents(two)).toEqual(["two-silent"]); + }); + + it("orders preceding context by message time, not by inbox row order", async () => { + const app = getApp(); + const uid = crypto.randomUUID().slice(0, 6); + const { human, observer, chat } = await seedChatWithObserver(app, uid); + + const first = await sendSilent(app, chat.id, human.agent.uuid, "written-first"); + const second = await sendSilent(app, chat.id, human.agent.uuid, "written-second"); + const third = await sendSilent(app, chat.id, human.agent.uuid, "written-third"); + + // Make message chronology disagree with inbox insertion order: the row + // written second is now the oldest message. Ordering by the inbox row + // would return written-first/second/third; ordering by message time — + // the actual contract — returns written-second/first/third. + const base = Date.now() - 60_000; + await app.db + .update(messages) + .set({ createdAt: new Date(base) }) + .where(eq(messages.id, second.message.id)); + await app.db + .update(messages) + .set({ createdAt: new Date(base + 1_000) }) + .where(eq(messages.id, first.message.id)); + await app.db + .update(messages) + .set({ createdAt: new Date(base + 2_000) }) + .where(eq(messages.id, third.message.id)); + + await sendTrigger(app, chat.id, human.agent.uuid, observer.agent.uuid, "order-trigger"); + + const claimed = await inboxService.claimBacklogForPush(app.db, observer.agent.inboxId, 10); + const delivery = claimed[0]; + if (!delivery) throw new Error("expected trigger delivery"); + expect(precedingContents(delivery)).toEqual(["written-second", "written-first", "written-third"]); + // Rendered timestamps must stay ISO 8601 — the statement formats them in + // SQL because a raw execute returns PostgreSQL's own text form. + for (const preceding of delivery.message.precedingMessages) { + expect(preceding.createdAt).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); + } + }); + + it("closes the window at an already-acked earlier notify row", async () => { + const app = getApp(); + const uid = crypto.randomUUID().slice(0, 6); + const { human, observer, chat } = await seedChatWithObserver(app, uid); + + await sendSilent(app, chat.id, human.agent.uuid, "before-acked-trigger"); + await sendTrigger(app, chat.id, human.agent.uuid, observer.agent.uuid, "acked-trigger"); + + const firstClaim = await inboxService.claimBacklogForPush(app.db, observer.agent.inboxId, 10); + const firstDelivery = firstClaim[0]; + if (!firstDelivery) throw new Error("expected first delivery"); + const acked = await inboxService.ackEntryByIdForBoundAgents(app.db, firstDelivery.id, [observer.agent.inboxId]); + expect(acked.ok).toBe(true); + + await sendSilent(app, chat.id, human.agent.uuid, "after-acked-trigger"); + await sendTrigger(app, chat.id, human.agent.uuid, observer.agent.uuid, "second-trigger"); + + const secondClaim = await inboxService.claimBacklogForPush(app.db, observer.agent.inboxId, 10); + const secondDelivery = secondClaim[0]; + if (!secondDelivery) throw new Error("expected second delivery"); + // The bound query has no status filter on purpose: an acked notify row + // still closes the window, so the pre-trigger silent row stays out. + expect(precedingContents(secondDelivery)).toEqual(["after-acked-trigger"]); + }); + + it("skips rows with no chat while still bundling chat-scoped triggers", async () => { + const app = getApp(); + const uid = crypto.randomUUID().slice(0, 6); + const { human, observer, chat } = await seedChatWithObserver(app, uid); + + await sendSilent(app, chat.id, human.agent.uuid, "scoped-silent"); + const trigger = await sendTrigger(app, chat.id, human.agent.uuid, observer.agent.uuid, "scoped-trigger"); + + // A legacy null-chat row claimed in the same drain must not break the + // batch, and must carry no context of its own. + await app.db.insert(inboxEntries).values({ + inboxId: observer.agent.inboxId, + messageId: trigger.message.id, + chatId: null, + status: "pending", + notify: true, + }); + + const claimed = await inboxService.claimBacklogForPush(app.db, observer.agent.inboxId, 10); + expect(claimed).toHaveLength(2); + const scoped = claimed.find((entry) => entry.chatId === chat.id); + const unscoped = claimed.find((entry) => entry.chatId === null); + if (!scoped || !unscoped) throw new Error("expected both scoped and null-chat entries"); + expect(precedingContents(scoped)).toEqual(["scoped-silent"]); + expect(unscoped.message.precedingMessages).toEqual([]); + }); + + it("issues the same number of queries no matter how many triggers a drain claims", async () => { + const app = getApp(); + + async function drainAndCount(triggerCount: number): Promise<{ execute: number; select: number }> { + const uid = crypto.randomUUID().slice(0, 6); + const { human, observer, chat } = await seedChatWithObserver(app, uid); + for (let i = 0; i < triggerCount; i++) { + await sendSilent(app, chat.id, human.agent.uuid, `silent-${i}`); + await sendTrigger(app, chat.id, human.agent.uuid, observer.agent.uuid, `trigger-${i}`); + } + + const probe = countingDb(app.db); + const claimed = await inboxService.claimBacklogForPush(probe.db, observer.agent.inboxId, 50); + expect(claimed).toHaveLength(triggerCount); + expect(claimed.map(precedingContents)).toEqual(Array.from({ length: triggerCount }, (_, i) => [`silent-${i}`])); + return probe.counts(); + } + + const small = await drainAndCount(2); + const large = await drainAndCount(8); + + // Counting both statement kinds matters: `execute` alone would still read + // as 1 if someone reintroduced a per-trigger `select` alongside the + // batched statement. + expect(large).toEqual(small); + // The batched preceding-context query is the only raw statement on this + // path. The per-trigger loop this replaced would report `triggerCount`. + expect(large.execute).toBe(1); + }); + + it("skips silent rows another transaction holds instead of blocking on them", async () => { + const app = getApp(); + const uid = crypto.randomUUID().slice(0, 6); + const { human, observer, chat } = await seedChatWithObserver(app, uid); + + const held = await sendSilent(app, chat.id, human.agent.uuid, "held-silent"); + await sendSilent(app, chat.id, human.agent.uuid, "free-silent"); + await sendTrigger(app, chat.id, human.agent.uuid, observer.agent.uuid, "concurrent-trigger"); + + const [heldRow] = await app.db + .select({ id: inboxEntries.id }) + .from(inboxEntries) + .where(and(eq(inboxEntries.inboxId, observer.agent.inboxId), eq(inboxEntries.messageId, held.message.id))) + .orderBy(asc(inboxEntries.id)) + .limit(1); + if (!heldRow) throw new Error("expected the silent row to exist"); + + // Hold a row lock in a parallel transaction, then drain. SKIP LOCKED must + // step over the held row and still return promptly. + let releaseLock: () => void = () => {}; + const lockReleased = new Promise((resolve) => { + releaseLock = resolve; + }); + const lockHeld = new Promise((resolve, reject) => { + app.db + .transaction(async (tx) => { + await tx.select().from(inboxEntries).where(eq(inboxEntries.id, heldRow.id)).for("update"); + resolve(); + await lockReleased; + }) + .catch(reject); + }); + await lockHeld; + + try { + const claimed = await inboxService.claimBacklogForPush(app.db, observer.agent.inboxId, 10); + const delivery = claimed[0]; + if (!delivery) throw new Error("expected trigger delivery"); + expect(precedingContents(delivery)).toEqual(["free-silent"]); + } finally { + releaseLock(); + } + }); +}); + +/** + * Wrap a database so statements issued inside a delivery transaction can be + * counted. Both `execute` and `select` are tracked — counting only raw + * statements would miss a per-trigger `select` reintroduced next to the + * batched one. + * + * The casts are unavoidable: Drizzle's transaction callback is generically + * typed over the schema, and a `Proxy` cannot preserve that relationship. The + * proxy only intercepts `transaction`, `execute`, and `select`, forwarding + * everything else untouched, so runtime behaviour is identical. + */ +function countingDb(db: FastifyInstance["db"]): { + db: FastifyInstance["db"]; + counts: () => { execute: number; select: number }; +} { + const tally = { execute: 0, select: 0 }; + const wrapped = new Proxy(db, { + get(target, prop, receiver) { + if (prop !== "transaction") return Reflect.get(target, prop, receiver); + return (callback: (tx: unknown) => Promise, ...rest: unknown[]) => + target.transaction( + async (tx) => { + const txProxy = new Proxy(tx, { + get(txTarget, txProp, txReceiver) { + const value = Reflect.get(txTarget, txProp, txReceiver); + const counted = txProp === "execute" || txProp === "select"; + if (!counted || typeof value !== "function") return value; + return (...args: unknown[]) => { + tally[txProp] += 1; + return value.apply(txTarget, args); + }; + }, + }); + return callback(txProxy); + }, + // biome-ignore lint/suspicious/noExplicitAny: Proxy cannot carry Drizzle's schema-generic transaction signature + ...(rest as any), + ); + }, + }); + return { db: wrapped, counts: () => ({ ...tally }) }; +} diff --git a/packages/server/src/__tests__/ws-client-branch-fake.test.ts b/packages/server/src/__tests__/ws-client-branch-fake.test.ts index b12fc549f..bd9034ea6 100644 --- a/packages/server/src/__tests__/ws-client-branch-fake.test.ts +++ b/packages/server/src/__tests__/ws-client-branch-fake.test.ts @@ -326,6 +326,9 @@ describe("Agent client WS branch fakes", () => { function activeAgentRow(overrides: Record = {}): Record { return { id: "agent_1", + // Batched route validation reads `agents.uuid` to map each returned row + // back to its agent; a single-row lookup could leave it implicit. + uuid: "agent_1", displayName: "Agent", type: "agent", organizationId: "org_1", diff --git a/packages/server/src/api/agent/ws-client.ts b/packages/server/src/api/agent/ws-client.ts index 3762af026..416df2a95 100644 --- a/packages/server/src/api/agent/ws-client.ts +++ b/packages/server/src/api/agent/ws-client.ts @@ -438,34 +438,82 @@ export function clientWsRoutes(notifier: Notifier, instanceId: string) { app.log.info({ clientId, agentId, reason }, "dropped stale local agent binding"); } - async function ensureAgentStillRoutedHere(agentId: string): Promise { - if (!isAgentStillRoutedHere(agentId)) return false; - const info = boundAgents.get(agentId); - if (!info || !clientId) return false; - const [row] = await app.db + /** + * Authoritative route check for several bindings in one round-trip. + * + * A socket can hold many bindings and there is no hard per-client agent + * cap, so validating them one at a time made ACK and heartbeat cost grow + * with fleet size. The judgement per agent is unchanged; only the + * fetch is batched. + * + * Returns the ids still routed to this socket. Agents whose + * authoritative route moved away are dropped with the usual side + * effects (`dropLocalAgentBinding`). + */ + async function ensureAgentsStillRoutedHere(agentIds: readonly string[]): Promise> { + const routed = new Set(); + if (!clientId) return routed; + // Snapshot before querying: `dropLocalAgentBinding` mutates + // `boundAgents`, so deciding the candidate set up front keeps the + // outcome independent of drop order. The in-memory gate is free, so + // only survivors reach the database. + const candidates = agentIds.filter((agentId) => isAgentStillRoutedHere(agentId)); + if (candidates.length === 0) return routed; + + const rows = await app.db .select({ + uuid: agents.uuid, clientId: agents.clientId, runtimeProvider: agents.runtimeProvider, status: agents.status, metadata: agents.metadata, }) .from(agents) - .where(eq(agents.uuid, agentId)) - .limit(1); - if (row?.clientId === clientId && row.status === "active" && row.runtimeProvider === info.runtimeProvider) { - return true; - } - const switchClaim = agentRuntimeSwitchService.getRuntimeSwitchClaim(row?.metadata); - if ( - row?.status === "suspended" && - switchClaim?.phase === "claimed" && - switchClaim.oldClientId === clientId && - switchClaim.oldRuntimeProvider === info.runtimeProvider - ) { - return false; + .where(inArray(agents.uuid, candidates)); + const rowByAgentId = new Map(rows.map((row) => [row.uuid, row])); + + for (const agentId of candidates) { + // Read the binding *after* the query, not before. A rebind updates + // the row and the local binding together, so whichever side is read + // first can go stale and report a provider mismatch that drops an + // agent which had just rebound successfully. + // + // This relocates that window rather than closing it. `row` is a + // snapshot from the moment Postgres executed the SELECT, so reading + // the binding afterwards exposes [snapshot .. result delivery + + // event loop turn] where reading it first exposed [binding read .. + // snapshot] — two halves of the same round-trip, comparable in + // width. Neither is negligible: the socket's message handler is not + // awaited by the emitter, and bind frames are not serialised + // against heartbeat frames, so the interleaving is reachable. + const info = boundAgents.get(agentId); + if (!info) continue; + const row = rowByAgentId.get(agentId); + if (row?.clientId === clientId && row.status === "active" && row.runtimeProvider === info.runtimeProvider) { + routed.add(agentId); + continue; + } + // A claimed runtime switch parks the binding rather than dropping + // it: the agent is no longer routed here, but the local binding has + // to survive so the switch can still be completed or aborted. + const switchClaim = agentRuntimeSwitchService.getRuntimeSwitchClaim(row?.metadata); + if ( + row?.status === "suspended" && + switchClaim?.phase === "claimed" && + switchClaim.oldClientId === clientId && + switchClaim.oldRuntimeProvider === info.runtimeProvider + ) { + continue; + } + dropLocalAgentBinding(agentId, "authoritative_route_changed"); } - dropLocalAgentBinding(agentId, "authoritative_route_changed"); - return false; + return routed; + } + + /** Single-agent form. Delegates so there is exactly one implementation + * of the route judgement and its drop side effects. */ + async function ensureAgentStillRoutedHere(agentId: string): Promise { + return (await ensureAgentsStillRoutedHere([agentId])).has(agentId); } function inboxInFlightCount(agentId: string): number { @@ -1630,12 +1678,11 @@ export function clientWsRoutes(notifier: Notifier, instanceId: string) { await chainInboxDelivery("__socket", async () => { try { - const routedBoundAgents = []; - for (const agent of boundAgents.values()) { - if (await ensureAgentStillRoutedHere(agent.agentId)) { - routedBoundAgents.push(agent); - } - } + // Keep whole binding records, not just ids: the ack result + // is mapped back to its owning agent by `inboxId` below. + const boundSnapshot = [...boundAgents.values()]; + const routedIds = await ensureAgentsStillRoutedHere(boundSnapshot.map((agent) => agent.agentId)); + const routedBoundAgents = boundSnapshot.filter((agent) => routedIds.has(agent.agentId)); const ackResult = await inboxService.ackEntryByIdForBoundAgents( app.db, entryId, @@ -1768,10 +1815,7 @@ export function clientWsRoutes(notifier: Notifier, instanceId: string) { }); } else if (type === "heartbeat") { if (clientId && connectionManager.isActiveClientConnection(clientId, socket)) { - const routedAgentIds = []; - for (const id of boundAgents.keys()) { - if (await ensureAgentStillRoutedHere(id)) routedAgentIds.push(id); - } + const routedAgentIds = [...(await ensureAgentsStillRoutedHere([...boundAgents.keys()]))]; const pausedReason = msg && typeof msg === "object" && "pausedReason" in msg ? ((msg as { pausedReason?: "auth_rejected" | "auth_refresh_failed" | null }).pausedReason ?? null) @@ -1782,11 +1826,20 @@ export function clientWsRoutes(notifier: Notifier, instanceId: string) { routedAgentIds, pausedReason, }); + // No second route check here. `restoredAgentIds` is already a + // subset of `routedAgentIds` (it is filtered by an `EXISTS` on + // the same client + active status), and the repair itself + // re-validates: `maybeRepairInboxBacklog` gates on the + // in-memory route, then `drainBacklogForAgent` opens with its + // own `ensureAgentStillRoutedHere` — a strictly later, and so + // strictly fresher, authoritative check. Re-reading here would + // add one query per bound agent on every heartbeat and close + // no race. The only residual effect is that an agent that just + // moved away can still consume one repair-throttle slot, which + // is meaningless for a binding that is going away. const repairableAgentIds = new Set(liveness.restoredAgentIds); for (const info of boundAgents.values()) { - if (repairableAgentIds.has(info.agentId) && (await ensureAgentStillRoutedHere(info.agentId))) { - maybeRepairInboxBacklog(info.agentId, info.inboxId); - } + if (repairableAgentIds.has(info.agentId)) maybeRepairInboxBacklog(info.agentId, info.inboxId); } await reconcilePinnedAgentsForClient(); } diff --git a/packages/server/src/db/schema/inbox-entries.ts b/packages/server/src/db/schema/inbox-entries.ts index 3e8fa5ef6..53164b60c 100644 --- a/packages/server/src/db/schema/inbox-entries.ts +++ b/packages/server/src/db/schema/inbox-entries.ts @@ -44,11 +44,37 @@ export const inboxEntries = pgTable( .on(table.inboxId, table.createdAt) .where(sql`status = 'pending' AND notify = true`), /** - * Bundling lookup: given a notify=true trigger, find all silent pending - * rows in the same chat that should be attached as preceding context. - * Composite shape mirrors the actual WHERE clause used in pollInbox. + * Bundling lookup: given a notify=true trigger, find the silent pending + * rows in the same chat to attach as preceding context. + * + * `id` is indexed because every caller bounds the window by id — the + * preceding-context window (`> previous notify cursor`, `< trigger`) and + * ACK-through's `id <= cursor` drain. Inside a LATERAL those bounds are + * correlated values whose width the planner cannot estimate, so without + * `id` it scans every silent row in the chat and discards almost all of + * them. + * + * Partial rather than a wider composite: `status`/`notify` move into the + * predicate instead of the key. A key ending in the unique `id` defeats + * B-tree deduplication — the four-column form compresses ~240k rows into + * ~7 bytes each, while appending `id` makes every key unique and costs + * ~49 bytes each. Restricting the row set instead keeps the index small, + * and `inbox_entries` is append-only, so excluding consumed rows matters + * more over time than it does today. */ - index("idx_inbox_chat_silent").on(table.inboxId, table.chatId, table.notify, table.status), + index("idx_inbox_chat_silent_pending") + .on(table.inboxId, table.chatId, table.id) + .where(sql`status = 'pending' AND notify = false`), + /** + * Notify-cursor lookup: the previous trigger before a given entry, and + * ACK-through's contiguous notify prefix. + * + * Deliberately not filtered on `status` — both callers must see notify + * rows in any state. An already-acked trigger still closes a preceding- + * context window, and ACK-through has to walk acked rows to prove the + * prefix has no gap. + */ + index("idx_inbox_chat_notify").on(table.inboxId, table.chatId, table.id).where(sql`notify = true`), /** * Message-history delivery status lookup. The chat messages API checks * whether any inbox row for a message is acked, delivered, or still diff --git a/packages/server/src/services/inbox.ts b/packages/server/src/services/inbox.ts index 62ab8d04a..a813d409a 100644 --- a/packages/server/src/services/inbox.ts +++ b/packages/server/src/services/inbox.ts @@ -4,7 +4,7 @@ import { messageSourceSchema, type PrecedingMessage, } from "@first-tree/shared"; -import { and, asc, desc, eq, gt, inArray, isNull, lt, sql } from "drizzle-orm"; +import { and, asc, desc, eq, inArray, isNull, sql } from "drizzle-orm"; import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core"; import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"; import type { Database } from "../db/connection.js"; @@ -28,8 +28,10 @@ export type AckEntryResult = } | { ok: false; reason: "not_found_or_not_bound" | "non_notify" | "prefix_gap" }; -/** Structurally-typed DB so both `Database` and transaction clients work. */ -type TxLike = Pick>, "select" | "update" | "delete" | "insert">; +/** Structurally-typed DB so both `Database` and transaction clients work. + * `execute` is included because the set-based preceding-context query below + * needs a raw statement (`LATERAL` has no Drizzle query-builder form). */ +type TxLike = Pick>, "select" | "update" | "delete" | "insert" | "execute">; /** Wider DB shape that matches both the concrete `Database` and the * `PgDatabase` widening used by sibling services (e.g. participant-mode). @@ -358,6 +360,20 @@ function fairBudgetKey(chatId: string | null): string { return chatId === null ? "null" : `chat:${chatId}`; } +/** + * Narrow a raw `inbox_entries.id` from `tx.execute` back to a number. + * + * Drizzle's `bigserial({ mode: "number" })` conversion only applies to + * query-builder selects; a raw statement hands the driver's own + * representation through, and postgres-js returns `bigint` columns as + * strings. `source` names the statement so a corrupt id is traceable. + */ +function toInboxEntryId(raw: string | number, source: string): number { + const id = typeof raw === "number" ? raw : Number(raw); + if (!Number.isSafeInteger(id)) throw new Error(`Unexpected inbox entry id from ${source}: ${raw}`); + return id; +} + /** * Fair WS backlog path for normal agent drains. It keeps the old backlog * helper's same-chat id order, but chooses across chats by chat-local rank so @@ -435,11 +451,7 @@ export async function claimBacklogForPushFair( ORDER BY locked.chat_rank, locked.id `); - const targetIds = selected.map((row) => { - const id = typeof row.id === "number" ? row.id : Number(row.id); - if (!Number.isSafeInteger(id)) throw new Error(`Unexpected inbox entry id from fair claim: ${row.id}`); - return id; - }); + const targetIds = selected.map((row) => toInboxEntryId(row.id, "fair claim")); if (targetIds.length === 0) return []; const claimed = await tx @@ -453,6 +465,27 @@ export async function claimBacklogForPushFair( ); } +/** + * One bundled silent row from the batched preceding-context query, keyed back + * to the trigger it belongs to. + * + * The shape mirrors raw driver output because `tx.execute` bypasses Drizzle's + * column mapping: `bigserial` arrives as a string (see `toInboxEntryId`) and + * `timestamptz` as PostgreSQL's own text form, not a `Date`. `jsonb` is the + * one type the driver still parses. `created_at` is therefore formatted to + * ISO 8601 in SQL rather than re-parsed here — see the statement below. + */ +type PrecedingContextRow = { + trigger_entry_id: string | number; + message_id: string; + sender_id: string; + format: string; + content: unknown; + metadata: Record | null; + source: string | null; + created_at: string; +}; + /** * Per claimed trigger: SELECT silent (notify=false) pending rows in the same * chat that occurred between the previous trigger in this batch (or beginning @@ -463,6 +496,13 @@ export async function claimBacklogForPushFair( * consumption: recovery must be able to reset the notify trigger and rebuild * the same trigger-relative context window. Silent rows are drained only when * the client ACKs the consumed notify entry. + * + * **One statement, not one per trigger.** Each trigger's lower bound is + * statically derivable: within a chat, `trigger[i]`'s bound is + * `trigger[i-1].id`, and only `trigger[0]` has to look outside the batch for + * the preceding notify row. Nothing depends on a previous iteration's *result*, + * so the whole fan-out collapses into a single set-based statement. A 50-entry + * drain used to hold its transaction open across 50+ sequential round-trips. */ async function collectPrecedingContext( tx: TxLike, @@ -471,105 +511,144 @@ async function collectPrecedingContext( ): Promise> { const result = new Map(); - // Group triggers by chatId so we can split the silent timeline per chat. - const byChat = new Map>>(); - for (const t of triggers) { - if (t.chatId === null) continue; // defensive: legacy / null-chatId rows can't have context - const list = byChat.get(t.chatId) ?? []; - list.push(t); - byChat.set(t.chatId, list); - } + // Defensive: legacy / null-chatId rows can't have chat-scoped context. + const scopedTriggers = triggers.filter((t) => t.chatId !== null); + if (scopedTriggers.length === 0) return result; + + // Pre-seed every in-scope trigger so a trigger whose window is empty still + // maps to `[]` rather than being absent. + for (const trigger of scopedTriggers) result.set(trigger.id, []); + + const triggerInput = JSON.stringify( + scopedTriggers.map((trigger) => ({ + entry_id: trigger.id, + chat_id: trigger.chatId, + // Computed here rather than as a SQL `interval` so the 24h boundary + // stays bit-identical to the per-trigger implementation this replaced. + window_start: new Date(trigger.createdAt.getTime() - PRECEDING_CONTEXT_WINDOW_SECONDS * 1000).toISOString(), + })), + ); - for (const [chatId, chatTriggers] of byChat) { - chatTriggers.sort((a, b) => a.id - b.id); - - const firstTrigger = chatTriggers[0]; - if (!firstTrigger) continue; - const [previousNotify] = await tx - .select({ id: inboxEntries.id }) - .from(inboxEntries) - .where( - and( - eq(inboxEntries.inboxId, inboxId), - eq(inboxEntries.chatId, chatId), - eq(inboxEntries.notify, true), - lt(inboxEntries.id, firstTrigger.id), - ), - ) - .orderBy(desc(inboxEntries.id)) - .limit(1); - - // For each trigger, fetch silent context strictly before it (and after - // the previous notify trigger cursor, even if that trigger was delivered - // in an earlier unacked batch). Window: 24h before the trigger. - // - // Order matters: when there are MORE than `PRECEDING_CONTEXT_MAX_ENTRIES` - // candidates, we want to keep the rows CLOSEST to the trigger (most - // contextually relevant) and drop the oldest. So select DESC + LIMIT, - // then reverse in JS to get chronological prompt-ready output. Selecting - // ASC + LIMIT would drop the recent rows; ACK-through later drains every - // silent row behind the consumed notify cursor, including rows excluded by - // this cap, so this delivery must choose the most relevant window now. - // - // We sort by `messages.createdAt` rather than `inboxEntries.createdAt` - // because `addParticipant`'s backfill writes 50 inbox rows in one - // `INSERT VALUES (...)` — they all share `statement_timestamp()`. The - // message rows themselves have distinct, monotonic timestamps (uuidv7 - // ids are time-ordered, `messages.created_at` is the authoritative - // chronology), so ordering by the joined message timestamp is the only - // stable contract the prompt-rendered context can rely on. - // - // Concurrency: `FOR UPDATE OF inboxEntries SKIP LOCKED` prevents two - // parallel polls on the same inbox from bundling the same silent row - // twice. Without it, poll A picking trigger T1 and poll B picking T2 - // (T2 > T1) would both include silent rows < T1 in their preceding - // context. With SKIP LOCKED, the second poll skips the rows the first - // has reserved. - let previousNotifyId: number | null = previousNotify?.id ?? null; - for (const trigger of chatTriggers) { - const windowStart = new Date(trigger.createdAt.getTime() - PRECEDING_CONTEXT_WINDOW_SECONDS * 1000); - const rows = await tx - .select({ - messageId: messages.id, - senderId: messages.senderId, - format: messages.format, - content: messages.content, - metadata: messages.metadata, - source: messages.source, - createdAt: messages.createdAt, - }) - .from(inboxEntries) - .innerJoin(messages, eq(messages.id, inboxEntries.messageId)) - .where( - and( - eq(inboxEntries.inboxId, inboxId), - eq(inboxEntries.chatId, chatId), - eq(inboxEntries.status, "pending"), - eq(inboxEntries.notify, false), - lt(inboxEntries.id, trigger.id), - previousNotifyId === null ? undefined : gt(inboxEntries.id, previousNotifyId), - gt(inboxEntries.createdAt, windowStart), - ), - ) - .orderBy(desc(messages.createdAt)) - .limit(PRECEDING_CONTEXT_MAX_ENTRIES) - .for("update", { of: inboxEntries, skipLocked: true }); - - // Reverse so the prompt-rendered block reads oldest → newest. - const preceding: PrecedingMessage[] = rows - .map((r) => ({ - id: r.messageId, - senderId: r.senderId, - format: r.format, - content: r.content, - metadata: (r.metadata ?? {}) as Record, - source: messageSourceSchema.nullable().catch(null).parse(r.source), - createdAt: r.createdAt.toISOString(), - })) - .reverse(); - result.set(trigger.id, preceding); - previousNotifyId = trigger.id; - } + // `jsonb_to_recordset` mirrors the batching idiom already used by + // `claimBacklogForPushFair` above. + // + // `batch_lower_bound_id` — `lag()` reproduces the old loop's + // `previousNotifyId = trigger.id` carry-over. The window function lives in + // its own CTE on purpose: PostgreSQL rejects `FOR UPDATE` in any query level + // that also has a window function, and rejects it on the nullable side of an + // outer join. Keeping the lock *inside* the `CROSS JOIN LATERAL` subquery is + // the only shape that satisfies both restrictions. + // + // The scalar subquery resolves the bound for the first trigger of each chat + // only — `COALESCE` short-circuits for the rest. It deliberately has no + // status filter: any earlier notify row, including an already-acked one, + // closes the window. + // + // Concurrency: `FOR UPDATE OF e SKIP LOCKED` prevents two parallel polls on + // the same inbox from bundling the same silent row twice. Batching does not + // weaken this — the per-trigger ranges `(trigger[i-1].id, trigger[i].id)` + // are disjoint by construction, so a single statement locks exactly the rows + // the sequential loop would have. + // + // Ordering (unchanged): the LATERAL takes `messages.created_at DESC` + + // `LIMIT` so that when candidates exceed the cap we keep the rows CLOSEST to + // the trigger; ACK-through later drains the overflow anyway, so this delivery + // must pick the most relevant window now. The outer `ASC` then renders the + // prompt oldest → newest. Sorting by `messages.created_at` rather than + // `inbox_entries.created_at` is required because `addParticipant`'s backfill + // writes its rows in one `INSERT VALUES (...)`, sharing a single + // `statement_timestamp()`; only the message timestamps are distinct and + // monotonic. `m.id` (uuidv7, time-ordered) breaks ties so the batch is + // deterministic. The 24h floor still compares `inbox_entries.created_at` — + // that asymmetry is intentional and predates this change. + const rows = await tx.execute(sql` + WITH trigger_input AS ( + SELECT + t.entry_id, + t.chat_id, + t.window_start, + lag(t.entry_id) OVER (PARTITION BY t.chat_id ORDER BY t.entry_id) AS batch_lower_bound_id + FROM jsonb_to_recordset(${triggerInput}::jsonb) + AS t(entry_id bigint, chat_id text, window_start timestamptz) + ), + bounded AS ( + SELECT + ti.entry_id, + ti.chat_id, + ti.window_start, + COALESCE( + ti.batch_lower_bound_id, + ( + SELECT prev.id + FROM inbox_entries prev + WHERE prev.inbox_id = ${inboxId} + AND prev.chat_id = ti.chat_id + AND prev.notify = true + AND prev.id < ti.entry_id + ORDER BY prev.id DESC + LIMIT 1 + ) + ) AS lower_bound_id + FROM trigger_input ti + ) + SELECT + b.entry_id AS trigger_entry_id, + ctx.message_id, + ctx.sender_id, + ctx.format, + ctx.content, + ctx.metadata, + ctx.source, + ctx.created_at + FROM bounded b + CROSS JOIN LATERAL ( + SELECT + m.id AS message_id, + m.sender_id, + m.format, + m.content, + m.metadata, + m.source, + -- Rendered in SQL because a raw statement hands back PostgreSQL's own + -- timestamp text (2026-07-20 00:01:00.123456+00), which is not + -- ISO 8601. Formatting here matches Date#toISOString() exactly -- + -- millisecond precision, T separator, Z suffix -- instead of relying + -- on JS to parse a non-standard shape. + to_char(m.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS created_at, + m.created_at AS sort_created_at + FROM inbox_entries e + INNER JOIN messages m ON m.id = e.message_id + WHERE e.inbox_id = ${inboxId} + AND e.chat_id = b.chat_id + AND e.status = 'pending' + AND e.notify = false + AND e.id < b.entry_id + AND (b.lower_bound_id IS NULL OR e.id > b.lower_bound_id) + AND e.created_at > b.window_start + ORDER BY m.created_at DESC, m.id DESC + LIMIT ${PRECEDING_CONTEXT_MAX_ENTRIES} + FOR UPDATE OF e SKIP LOCKED + ) ctx + -- The LATERAL's inner ORDER BY only decides which rows LIMIT keeps; that + -- order is not carried to the outer query. Order explicitly here (on the + -- raw timestamp, not the rendered text) so the prompt block reads + -- oldest → newest regardless of the plan chosen. + ORDER BY b.entry_id, ctx.sort_created_at ASC, ctx.message_id ASC + `); + + for (const row of rows) { + const bucket = result.get(toInboxEntryId(row.trigger_entry_id, "preceding context")); + // The statement only ever echoes trigger ids we sent in; skip defensively. + if (!bucket) continue; + bucket.push({ + id: row.message_id, + senderId: row.sender_id, + format: row.format, + content: row.content, + metadata: row.metadata ?? {}, + source: messageSourceSchema.nullable().catch(null).parse(row.source), + createdAt: row.created_at, + }); } return result;