diff --git a/packages/server/drizzle/0091_green_archangel.sql b/packages/server/drizzle/0091_green_archangel.sql new file mode 100644 index 000000000..391b9bbb7 --- /dev/null +++ b/packages/server/drizzle/0091_green_archangel.sql @@ -0,0 +1,58 @@ +-- ACK-through cursor window index for inbox_entries. +-- +-- `ackThroughEntryIdForBoundAgents` commits the contiguous notify=true prefix +-- of an (inbox_id, chat_id) partition up to an acked cursor. Before this index +-- no index matched that query: the planner fell back to the primary key and +-- scanned — and, under FOR UPDATE, locked — every notify row ever written to +-- the chat, including long-acked ones that can never change the outcome. Cost +-- per ACK was therefore O(chat history) and lifetime cost O(N^2), with lock +-- contention growing to match. +-- +-- The index is partial on `status <> 'acked'` because `acked` is terminal: +-- nothing resets an acked row back to pending/delivered, so the non-acked set +-- is exactly the live in-flight window. That keeps the index sized to +-- concurrent traffic rather than to history — measured at 48 kB on a 2.7M-row +-- table whose comparable full index (idx_inbox_chat_silent) was 26 MB. +-- +-- Column order is load-bearing: +-- +-- inbox_id, chat_id — the partition the ACK commits within. +-- notify — kept in the key rather than in the predicate. A +-- partial index only applies when the planner can prove +-- the query implies its predicate, and under a generic +-- plan a bound parameter proves nothing. The service +-- passes `notify` as a parameter, so a `WHERE notify = +-- true` predicate would silently stop matching; as a key +-- column that same parameter is an ordinary index +-- condition. +-- id — last, so `id <= cursor` is a range condition and +-- `ORDER BY id` needs no sort (which also preserves the +-- ascending FOR UPDATE lock order). +-- +-- The predicate is spelled `status <> 'acked'` so the service's WHERE clause +-- can match it verbatim. The service must also pass that clause as a literal +-- rather than a bind parameter, for the same reason `notify` is not in the +-- predicate: under a generic plan PostgreSQL cannot prove a Param-based clause +-- implies a partial-index predicate, and the scan silently degrades back to +-- whole-partition. This index therefore depends on exactly one inlined +-- literal, and that literal is the fix itself. See services/inbox.ts. +-- +-- ──────────────── Operator note ──────────────── +-- +-- Drizzle migrator wraps every migration file in a single transaction (see the +-- comment block in 0020_unified_user_token.sql), so `CREATE INDEX +-- CONCURRENTLY` cannot be used here. The regular form below holds a SHARE lock +-- that blocks writes to `inbox_entries` (including message fan-out) for the +-- duration of one heap scan: measured at 30 ms for 400k rows and 73 ms for +-- 2.7M rows on a warm table, and proportional to table size on a cold one. +-- +-- For a large production table, the runbook is: +-- +-- 1. Stop applying new migrations briefly. +-- 2. Manually run, OUTSIDE a transaction: +-- CREATE INDEX CONCURRENTLY idx_inbox_unacked_cursor +-- ON inbox_entries (inbox_id, chat_id, notify, id) +-- WHERE status <> 'acked'; +-- 3. Re-run `pnpm db:migrate`. The `IF NOT EXISTS` clause below detects the +-- existing index and the statement becomes a no-op. +CREATE INDEX IF NOT EXISTS "idx_inbox_unacked_cursor" ON "inbox_entries" USING btree ("inbox_id","chat_id","notify","id") WHERE status <> 'acked'; diff --git a/packages/server/drizzle/LATEST b/packages/server/drizzle/LATEST index 70f12c5a8..98c1e361d 100644 --- a/packages/server/drizzle/LATEST +++ b/packages/server/drizzle/LATEST @@ -1 +1 @@ -0090_redundant_crystal +0091_green_archangel diff --git a/packages/server/drizzle/meta/0091_snapshot.json b/packages/server/drizzle/meta/0091_snapshot.json new file mode 100644 index 000000000..1a75124c6 --- /dev/null +++ b/packages/server/drizzle/meta/0091_snapshot.json @@ -0,0 +1,6274 @@ +{ + "id": "9318e58a-28c2-4db6-b0b4-7f164a7e95b2", + "prevId": "d0620311-f910-43cb-95a8-c81e740f1ce8", + "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 + }, + "template_ids": { + "name": "template_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "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": { + "idx_agent_configs_template_ids": { + "name": "idx_agent_configs_template_ids", + "columns": [ + { + "expression": "template_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ck_agent_configs_template_ids_cardinality": { + "name": "ck_agent_configs_template_ids_cardinality", + "value": "cardinality(\"agent_configs\".\"template_ids\") <= 3" + } + }, + "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 + }, + "origin_template_id": { + "name": "origin_template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_component_key": { + "name": "origin_component_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": {} + }, + "idx_agent_resource_bindings_template_origin": { + "name": "idx_agent_resource_bindings_template_origin", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"agent_resource_bindings\".\"origin_template_id\" IS NOT NULL", + "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": { + "ck_agent_resource_bindings_template_origin_consistency": { + "name": "ck_agent_resource_bindings_template_origin_consistency", + "value": "(\"agent_resource_bindings\".\"origin_template_id\" IS NULL AND \"agent_resource_bindings\".\"origin_component_key\" IS NULL) OR (\"agent_resource_bindings\".\"origin_template_id\" IS NOT NULL AND \"agent_resource_bindings\".\"origin_component_key\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.agent_templates": { + "name": "agent_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "replacement_template_id": { + "name": "replacement_template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "uq_agent_templates_slug": { + "name": "uq_agent_templates_slug", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_agent_templates_status": { + "name": "idx_agent_templates_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_templates_replacement_template_id_agent_templates_id_fk": { + "name": "agent_templates_replacement_template_id_agent_templates_id_fk", + "tableFrom": "agent_templates", + "tableTo": "agent_templates", + "columnsFrom": [ + "replacement_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ck_agent_templates_status": { + "name": "ck_agent_templates_status", + "value": "\"agent_templates\".\"status\" IN ('draft', 'active', 'retired')" + } + }, + "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 + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle_state": { + "name": "lifecycle_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "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": false + }, + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "attachments_org_state_idx": { + "name": "attachments_org_state_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lifecycle_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "attachments_state_updated_idx": { + "name": "attachments_state_updated_idx", + "columns": [ + { + "expression": "lifecycle_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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_inbound_at": { + "name": "last_system_hook_inbound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_project_hook_inbound_at": { + "name": "last_project_hook_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": { + "name": "idx_inbox_chat_silent", + "columns": [ + { + "expression": "inbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "notify", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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": {} + }, + "idx_inbox_unacked_cursor": { + "name": "idx_inbox_unacked_cursor", + "columns": [ + { + "expression": "inbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "notify", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status <> 'acked'", + "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": {} + }, + "idx_messages_attachment_image_id": { + "name": "idx_messages_attachment_image_id", + "columns": [ + { + "expression": "(\"content\" ->> 'imageId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_content_attachments": { + "name": "idx_messages_content_attachments", + "columns": [ + { + "expression": "((\"content\" -> 'attachments')) jsonb_path_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_messages_metadata_attachments": { + "name": "idx_messages_metadata_attachments", + "columns": [ + { + "expression": "((\"metadata\" -> 'attachments')) 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 + }, + "bundle_attachment_id": { + "name": "bundle_attachment_id", + "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 + }, + "origin_template_id": { + "name": "origin_template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_component_key": { + "name": "origin_component_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_content_digest": { + "name": "origin_content_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": {} + }, + "idx_resources_bundle_attachment": { + "name": "idx_resources_bundle_attachment", + "columns": [ + { + "expression": "bundle_attachment_id", + "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": {} + }, + "uq_resources_template_origin_active": { + "name": "uq_resources_template_origin_active", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_component_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"resources\".\"status\" IN ('active', 'stale') AND \"resources\".\"origin_template_id\" 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": { + "ck_resources_template_origin_consistency": { + "name": "ck_resources_template_origin_consistency", + "value": "(\"resources\".\"origin_template_id\" IS NULL AND \"resources\".\"origin_component_key\" IS NULL AND \"resources\".\"origin_content_digest\" IS NULL) OR (\"resources\".\"origin_template_id\" IS NOT NULL AND \"resources\".\"origin_component_key\" IS NOT NULL AND \"resources\".\"origin_content_digest\" IS NOT NULL)" + } + }, + "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 544a8a5bc..2f162ddca 100644 --- a/packages/server/drizzle/meta/_journal.json +++ b/packages/server/drizzle/meta/_journal.json @@ -638,6 +638,13 @@ "when": 1785410740640, "tag": "0090_redundant_crystal", "breakpoints": true + }, + { + "idx": 91, + "version": "7", + "when": 1785464005678, + "tag": "0091_green_archangel", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/server/src/__tests__/inbox-ack-scaling.test.ts b/packages/server/src/__tests__/inbox-ack-scaling.test.ts new file mode 100644 index 000000000..97f0e7a5f --- /dev/null +++ b/packages/server/src/__tests__/inbox-ack-scaling.test.ts @@ -0,0 +1,252 @@ +import { and, asc, eq, sql } from "drizzle-orm"; +import type { FastifyInstance } from "fastify"; +import { describe, expect, it } from "vitest"; +import { inboxEntries } from "../db/schema/inbox-entries.js"; +import * as inboxService from "../services/inbox.js"; +import { createTestAgent, useTestApp } from "./helpers.js"; + +/** + * Regression guards for PERF-008: ACK-through must cost the in-flight delta, + * not the chat's history. + * + * Before the fix, `ackThroughEntryIdForBoundAgents` selected — and, under + * `FOR UPDATE`, locked — every notify row in the partition up to the cursor, + * including long-acked rows that can never change the outcome. These tests pin + * both halves of that defect: the scan volume and the lock footprint. + */ +describe("inbox ACK scales with the delta, not chat history", () => { + const getApp = useTestApp(); + + const HISTORY_ROWS = 3000; + + /** + * Seed `HISTORY_ROWS` already-acked notify rows into one (inbox, chat) + * partition. Written as bulk SQL rather than through the message API on + * purpose: the point is a long history, and 3000 HTTP round trips would + * dominate the test's runtime without changing what is being measured. + */ + async function seedAckedHistory(app: FastifyInstance) { + const uid = crypto.randomUUID().slice(0, 8); + const receiverName = `ackscale-r-${uid}`; + const sender = await createTestAgent(app, { name: `ackscale-s-${uid}` }); + const receiver = await createTestAgent(app, { name: receiverName }); + const chatRes = await sender.request("POST", "/api/v1/agent/chats", { + type: "group", + participantIds: [receiver.agent.uuid], + }); + const chatId: string = chatRes.json().id; + const inboxId = receiver.agent.inboxId; + + await app.db.execute(sql` + INSERT INTO messages (id, chat_id, sender_id, format, content, metadata, source, created_at) + SELECT ${`hist-${uid}-`} || g, ${chatId}, ${sender.agent.uuid}, 'text', + to_jsonb('history ' || g), '{}'::jsonb, 'api', + now() - make_interval(secs => ${HISTORY_ROWS} - g) + FROM generate_series(1, ${HISTORY_ROWS}) g + `); + await app.db.execute(sql` + INSERT INTO inbox_entries (inbox_id, message_id, chat_id, status, notify, created_at, delivered_at, acked_at) + SELECT ${inboxId}, ${`hist-${uid}-`} || g, ${chatId}, 'acked', true, + now() - make_interval(secs => ${HISTORY_ROWS} - g), + now() - make_interval(secs => ${HISTORY_ROWS} - g), + now() - make_interval(secs => ${HISTORY_ROWS} - g) + FROM generate_series(1, ${HISTORY_ROWS}) g + `); + // The planner needs statistics to prefer the partial index over a seq scan. + await app.db.execute(sql`ANALYZE inbox_entries`); + + return { sender, receiverName, chatId, inboxId }; + } + + /** Deliver one fresh notify row on top of the seeded history. */ + async function deliverOneOnTop( + app: FastifyInstance, + sender: Awaited>, + chatId: string, + receiverName: string, + inboxId: string, + ) { + await sender.request("POST", `/api/v1/agent/chats/${chatId}/messages`, { + format: "text", + content: "live trigger", + receiverNames: [receiverName], + }); + const delivered = await inboxService.claimBacklogForPush(app.db, inboxId, 10); + const entry = delivered.at(-1); + if (!entry) throw new Error("expected a delivered entry on top of the seeded history"); + return entry; + } + + /** Sum every row a plan node actually touched, across the whole plan tree. */ + function countTouchedRows(node: Record): number { + const actual = typeof node["Actual Rows"] === "number" ? node["Actual Rows"] : 0; + const filtered = typeof node["Rows Removed by Filter"] === "number" ? node["Rows Removed by Filter"] : 0; + const children = Array.isArray(node.Plans) ? (node.Plans as Array>) : []; + return actual + filtered + children.reduce((sum, child) => sum + countTouchedRows(child), 0); + } + + it("keeps the non-acked clause a literal so the partial index survives a generic plan", () => { + // First of the two guards, and the one that catches an edit to the + // service. `ne(inboxEntries.status, "acked")` reads better and produces a + // bound parameter, which PostgreSQL cannot use to prove the partial-index + // predicate once the statement switches to a generic plan — the scan then + // silently reverts to whole-partition. A plan-level assertion cannot see + // that change (it would be exercising the test's own SQL), so pin the + // property directly on the clause the service uses. + const compiled = getApp() + .db.select({ id: inboxEntries.id }) + .from(inboxEntries) + .where(inboxService.NOT_ACKED_PREFIX_ROW) + .toSQL(); + + expect(compiled.params).toEqual([]); + expect(compiled.sql).toContain("<> 'acked'"); + }); + + it("reads only the non-acked delta under a generic plan, not the whole notify history", async () => { + const app = getApp(); + const { sender, receiverName, chatId, inboxId } = await seedAckedHistory(app); + const entry = await deliverOneOnTop(app, sender, chatId, receiverName, inboxId); + + // Second guard: the planner contract the literal above depends on. It has + // to run under a forced generic plan, because that is the only mode where + // a partial index can stop matching — under a custom plan every spelling + // looks fine and the guard would be decorative. + // + // Note this deliberately models a path the application does not currently + // take: Drizzle goes through postgres-js `unsafe()`, so its statements are + // unnamed and always planned as custom, and `plan_cache_mode` does not + // reach them. This is the worst case the inline literal insures against + // (a named statement, from a `.prepare()` or a prepare-capable pooler), + // not a description of production behavior. + // + // `notify` and the cursor stay bound parameters here on purpose: that is + // how the service passes them, and keeping `notify` out of the index + // predicate is what lets a parameter still act as an index condition. + // + // Known boundary: this exercises an equivalent statement, not the query + // object the service builds. Together with the literal guard above it + // covers the clause and the index contract, but a future restructuring of + // the service's own WHERE (an added OR branch, say) could stop matching the + // index without either guard noticing. Closing that would mean exporting + // the query builder purely for the test; the coupling was judged not worth + // it, so the gap is recorded here instead of left implicit. + const statement = `ack_prefix_guard_${crypto.randomUUID().replace(/-/g, "")}`; + const plan = await app.db.transaction(async (tx) => { + await tx.execute(sql.raw(`SET LOCAL plan_cache_mode = force_generic_plan`)); + await tx.execute( + sql.raw(` + PREPARE ${statement} (text, text, boolean, bigint) AS + SELECT id, status, delivered_at FROM inbox_entries + WHERE inbox_id = $1 AND chat_id = $2 AND notify = $3 + AND status <> 'acked' AND id <= $4 + ORDER BY id + `), + ); + // EXECUTE arguments cannot themselves be bind parameters, so the values + // are inlined. That does not weaken the test: `force_generic_plan` above + // already forced the plan to be built without knowing them. + const literal = (value: string) => `'${value.replace(/'/g, "''")}'`; + const explained = await tx.execute<{ "QUERY PLAN": Array<{ Plan: Record }> }>( + sql.raw( + `EXPLAIN (ANALYZE, FORMAT JSON) EXECUTE ${statement}(${literal(inboxId)}, ${literal(chatId)}, true, ${entry.id})`, + ), + ); + // No DEALLOCATE on the error path: PostgreSQL discards a statement + // prepared inside a transaction when that transaction rolls back. + await tx.execute(sql.raw(`DEALLOCATE ${statement}`)); + return explained[0]?.["QUERY PLAN"]?.[0]?.Plan; + }); + if (!plan) throw new Error("EXPLAIN returned no plan"); + + // One delivered row is the entire committable delta. Slack is generous + // because the exact plan node is the planner's choice; what must hold is + // that the work does not scale with HISTORY_ROWS. Pre-fix, this query + // touched HISTORY_ROWS + 1 rows. + expect(countTouchedRows(plan)).toBeLessThan(50); + expect(JSON.stringify(plan)).toContain("idx_inbox_unacked_cursor"); + }); + + it("does not lock already-acked history, so an ACK cannot queue behind it", async () => { + const app = getApp(); + const { sender, receiverName, chatId, inboxId } = await seedAckedHistory(app); + const entry = await deliverOneOnTop(app, sender, chatId, receiverName, inboxId); + + const [oldestAcked] = await app.db + .select({ id: inboxEntries.id }) + .from(inboxEntries) + .where(and(eq(inboxEntries.inboxId, inboxId), eq(inboxEntries.chatId, chatId), eq(inboxEntries.status, "acked"))) + .orderBy(asc(inboxEntries.id)) + .limit(1); + if (!oldestAcked) throw new Error("expected seeded acked history"); + + // Hold a row lock on one long-acked row, then ACK through a cursor above + // it. The pre-fix prefix scan locked every notify row up to the cursor, so + // it would block here until this transaction released. The delta scan + // never touches acked rows, so the ACK must complete while the lock is + // still held. + let releaseHolder: (() => void) | undefined; + const holderReady = new Promise((resolveReady) => { + const holding = new Promise((resolveRelease) => { + releaseHolder = resolveRelease; + }); + void app.db.transaction(async (tx) => { + await tx + .select({ id: inboxEntries.id }) + .from(inboxEntries) + .where(eq(inboxEntries.id, oldestAcked.id)) + .for("update"); + resolveReady(); + await holding; + }); + }); + await holderReady; + + try { + const ackOrTimeout = await Promise.race([ + inboxService + .ackEntryByIdForBoundAgents(app.db, entry.id, [inboxId]) + .then((res) => ({ kind: "ack", res }) as const), + new Promise<{ kind: "timeout" }>((resolveTimeout) => + setTimeout(() => resolveTimeout({ kind: "timeout" }), 5_000), + ), + ]); + + expect(ackOrTimeout.kind).toBe("ack"); + if (ackOrTimeout.kind !== "ack") throw new Error("ACK blocked on already-acked history"); + expect(ackOrTimeout.res.ok).toBe(true); + if (!ackOrTimeout.res.ok) throw new Error("ack-through unexpectedly rejected"); + expect(ackOrTimeout.res.ackedEntryIds).toEqual([entry.id]); + } finally { + releaseHolder?.(); + } + }); + + it("commits correctly with a long acked prefix in front of the cursor", async () => { + const app = getApp(); + const { sender, receiverName, chatId, inboxId } = await seedAckedHistory(app); + const entry = await deliverOneOnTop(app, sender, chatId, receiverName, inboxId); + + const accepted = await inboxService.ackEntryByIdForBoundAgents(app.db, entry.id, [inboxId]); + expect(accepted.ok).toBe(true); + if (!accepted.ok) throw new Error("ack-through unexpectedly rejected"); + expect(accepted.disposition).toBe("acked"); + expect(accepted.ackedCount).toBe(1); + expect(accepted.ackedEntryIds).toEqual([entry.id]); + expect(accepted.throughEntry.status).toBe("acked"); + + // The seeded history is untouched: ACK commits the delta, not the prefix. + const stillAcked = await app.db + .select({ count: sql`count(*)::int` }) + .from(inboxEntries) + .where(and(eq(inboxEntries.inboxId, inboxId), eq(inboxEntries.chatId, chatId), eq(inboxEntries.status, "acked"))); + expect(stillAcked[0]?.count).toBe(HISTORY_ROWS + 1); + + // A duplicate ACK over the same long prefix stays a no-op. + const duplicate = await inboxService.ackEntryByIdForBoundAgents(app.db, entry.id, [inboxId]); + expect(duplicate.ok).toBe(true); + if (!duplicate.ok) throw new Error("duplicate ack unexpectedly rejected"); + expect(duplicate.disposition).toBe("already_acked"); + expect(duplicate.ackedCount).toBe(0); + }); +}); diff --git a/packages/server/src/__tests__/inbox-delivery-indexes.test.ts b/packages/server/src/__tests__/inbox-delivery-indexes.test.ts index 3828658e3..d1690114a 100644 --- a/packages/server/src/__tests__/inbox-delivery-indexes.test.ts +++ b/packages/server/src/__tests__/inbox-delivery-indexes.test.ts @@ -28,6 +28,26 @@ describe("inbox delivery indexes", () => { expect(rows[0]?.indexdef).toContain("USING btree (message_id, status)"); }); + it("creates the partial index the ACK-through cursor scan depends on", async () => { + const rows = await getDb().execute<{ indexdef: string }>(sql` + SELECT indexdef + FROM pg_indexes + WHERE schemaname = 'public' + AND tablename = 'inbox_entries' + AND indexname = 'idx_inbox_unacked_cursor' + `); + + expect(rows).toHaveLength(1); + // Both halves are load-bearing and pinned deliberately. The key order lets + // `id <= cursor` be a range condition (no sort, ascending FOR UPDATE lock + // order), and the predicate has to stay spelled exactly this way so the + // service's literal `status <> 'acked'` clause can match it — a differently + // spelled predicate would still be correct SQL but would stop the planner + // from proving the implication, silently restoring the O(history) scan. + expect(rows[0]?.indexdef).toContain("USING btree (inbox_id, chat_id, notify, id)"); + expect(rows[0]?.indexdef).toContain("WHERE (status <> 'acked'::text)"); + }); + 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-ws-push.test.ts b/packages/server/src/__tests__/inbox-ws-push.test.ts index e4a3131a6..14f1b9d0f 100644 --- a/packages/server/src/__tests__/inbox-ws-push.test.ts +++ b/packages/server/src/__tests__/inbox-ws-push.test.ts @@ -1,4 +1,4 @@ -import { and, asc, eq, inArray } from "drizzle-orm"; +import { and, asc, eq, inArray, sql } from "drizzle-orm"; import type { FastifyInstance } from "fastify"; import { describe, expect, it } from "vitest"; import { chatMembership } from "../db/schema/chat-membership.js"; @@ -728,6 +728,73 @@ describe("inbox WS data-plane claim helpers", () => { expect(accepted.ackedEntryIds).toEqual([second.id]); }); + it("ackEntryByIdForBoundAgents returns ackedEntryIds ascending when delivered and reset rows interleave", async () => { + // The commit list is assembled from two different row classes (delivered, + // and delivered-then-reset-to-pending by recovery). Interleave them so a + // future refactor that appends the classes as separate groups — or that + // trusts RETURNING's undefined row order — produces a visibly wrong order. + // `ws-client.ts` consumes this array as an ascending cursor list. + const app = getApp(); + const { a2, messageIds, rows } = await seedDeliverables(app, 4); + if (rows.length !== 4) throw new Error("expected four inbox rows"); + + for (const messageId of messageIds) { + await inboxService.claimAndBuildForPush(app.db, a2.agent.inboxId, messageId); + } + // Rows 0 and 2 look like recovery-reset deliveries; 1 and 3 stay delivered. + await app.db + .update(inboxEntries) + .set({ status: "pending" }) + .where(inArray(inboxEntries.id, [rows[0]?.id ?? 0, rows[2]?.id ?? 0])); + + const last = rows[3]; + if (!last) throw new Error("expected a fourth inbox row"); + const accepted = await inboxService.ackEntryByIdForBoundAgents(app.db, last.id, [a2.agent.inboxId]); + expect(accepted.ok).toBe(true); + if (!accepted.ok) throw new Error("ack-through unexpectedly rejected"); + expect(accepted.disposition).toBe("accepted_from_pending"); + expect(accepted.ackedEntryIds).toEqual(rows.map((row) => row.id)); + expect(accepted.ackedEntryIds).toEqual([...accepted.ackedEntryIds].sort((a, b) => a - b)); + }); + + it("ackEntryByIdForBoundAgents still rejects a prefix row whose status is outside the delivery enum", async () => { + // `ck_inbox_entries_status` is NOT VALID, so a legacy row can carry a + // status the current enum does not describe. This pins the prefix scan as + // an *exclusion* (`status <> 'acked'`) rather than an allow-list + // (`status IN ('pending','delivered')`): an allow-list drops such a row + // from the prefix entirely, so the gap check never sees it and the commit + // silently steps over it. Verified — swapping the clause to the allow-list + // form makes this case return ok:true instead of prefix_gap. + const app = getApp(); + const { a2, messageIds, rows } = await seedDeliverables(app, 2); + const first = rows[0]; + const second = rows[1]; + if (!first || !second) throw new Error("expected two inbox rows"); + + await inboxService.claimAndBuildForPush(app.db, a2.agent.inboxId, messageIds[1] ?? ""); + + // NOT VALID exempts pre-existing rows but still polices new writes, so the + // only way to reproduce a legacy row is to lift the constraint for the + // write and restore its exact definition afterwards. + const [constraint] = await app.db.execute<{ definition: string }>(sql` + SELECT pg_get_constraintdef(oid) AS definition + FROM pg_constraint WHERE conrelid = 'inbox_entries'::regclass AND conname = 'ck_inbox_entries_status' + `); + const definition = constraint?.definition; + if (!definition) throw new Error("ck_inbox_entries_status not found"); + + await app.db.execute(sql`ALTER TABLE inbox_entries DROP CONSTRAINT ck_inbox_entries_status`); + try { + await app.db.execute(sql`UPDATE inbox_entries SET status = 'failed' WHERE id = ${first.id}`); + + const rejected = await inboxService.ackEntryByIdForBoundAgents(app.db, second.id, [a2.agent.inboxId]); + expect(rejected).toEqual({ ok: false, reason: "prefix_gap" }); + } finally { + await app.db.execute(sql`UPDATE inbox_entries SET status = 'pending' WHERE id = ${first.id}`); + await app.db.execute(sql.raw(`ALTER TABLE inbox_entries ADD CONSTRAINT ck_inbox_entries_status ${definition}`)); + } + }); + it("ackEntryByIdForBoundAgents acks only entries in the supplied inbox set", async () => { const app = getApp(); const { a2, messageId } = await seedDeliverable(app); diff --git a/packages/server/src/db/schema/inbox-entries.ts b/packages/server/src/db/schema/inbox-entries.ts index 3e8fa5ef6..2d75616ac 100644 --- a/packages/server/src/db/schema/inbox-entries.ts +++ b/packages/server/src/db/schema/inbox-entries.ts @@ -55,6 +55,35 @@ export const inboxEntries = pgTable( * pending; keeping message_id first bounds that lookup by page size. */ index("idx_inbox_entries_message_status").on(table.messageId, table.status), + /** + * ACK-through cursor window. `ackThroughEntryIdForBoundAgents` walks the + * `(inbox_id, chat_id)` partition up to the acked cursor; without this + * index the planner falls back to the primary key and scans — and locks — + * every notify row ever written to the chat, which is what made ACK cost + * O(history) and lifetime cost O(N^2). + * + * `acked` is a terminal state (nothing resets an acked row back to + * `pending`/`delivered`), so restricting the index to non-acked rows keeps + * it sized to the live in-flight window instead of to chat history. + * + * Shape notes, all load-bearing: + * - `notify` sits in the key rather than in the predicate on purpose. + * A partial index is only usable when the planner can prove the query + * implies its predicate, and under a generic plan a bound parameter + * proves nothing. The service passes `notify` as a parameter + * (`eq(inboxEntries.notify, true)`), so a `WHERE notify = true` + * predicate would silently stop matching; as a key column the same + * parameter is just an ordinary index condition. This keeps the index + * dependent on exactly one inlined literal instead of two. + * - `id` is the last key column, so `id <= cursor` is an index range + * condition and `ORDER BY id` needs no sort. + * - The predicate is spelled `status <> 'acked'` to match the query + * clause verbatim; see the `NOT_ACKED_PREFIX_ROW` note in + * services/inbox.ts for why that clause must stay a literal. + */ + index("idx_inbox_unacked_cursor") + .on(table.inboxId, table.chatId, table.notify, table.id) + .where(sql`status <> 'acked'`), check("ck_inbox_entries_status", sql`${table.status} IN ('pending', 'delivered', 'acked')`), ], ); diff --git a/packages/server/src/services/inbox.ts b/packages/server/src/services/inbox.ts index 62ab8d04a..d44a29eac 100644 --- a/packages/server/src/services/inbox.ts +++ b/packages/server/src/services/inbox.ts @@ -575,6 +575,49 @@ async function collectPrecedingContext( return result; } +/** Prefix row shape the ACK-through commit reasons about. */ +type AckPrefixRow = Pick; + +/** + * Restricts the ACK prefix scan to rows that can still change state. + * + * Spelled as an inline literal instead of `ne(inboxEntries.status, "acked")`. + * A partial index applies only when the planner can prove the query implies its + * predicate, and a bound parameter in a *cached* (named) statement proves + * nothing. + * + * That does not happen on today's path. Drizzle issues every query through + * postgres-js `unsafe()`, which hardcodes `prepare: false` and is passed no + * options, so this statement is unnamed: a one-shot plan, always planned as + * custom, with the parameter substituted before planning. Measured through the + * real driver on PostgreSQL 16.14 and 17, the bound-parameter spelling keeps + * using `idx_inbox_unacked_cursor` — including under `plan_cache_mode = + * force_generic_plan`, because that setting only governs cached statements. + * + * The literal is therefore insurance, not a fix for an observed regression: it + * costs one inlined constant and stays correct if this query ever becomes a + * named statement (a Drizzle `.prepare()`, a driver change, a pooler that + * prepares). Do not "normalize" it into a Drizzle comparison helper. Exported + * so `inbox-ack-scaling.test.ts` can assert it still compiles to zero bind + * parameters. + * + * Separately from the literal-vs-parameter question above — and unlike it, + * this one does bite today — the predicate has to stay an *exclusion*. + * `status IN ('pending', 'delivered')` selects the same rows today and would + * match the same partial index, but it is an allow-list: `ck_inbox_entries_status` + * is NOT VALID, so a legacy row can carry a status outside today's enum, and an + * allow-list would drop that row from the prefix entirely — the gap check would + * never see it and the commit would silently step over it. Excluding only + * `acked` keeps every unexpected status visible to the gap check, which is + * where it must be handled. + * + * Finally, this has to stay a plain inequality against a NOT NULL column: SQL's + * `<>` is three-valued and would drop a NULL status, whereas the JS gap check + * below treats an unexpected status as a gap. `inbox_entries.status` is NOT + * NULL, so the two agree; relaxing that column would reintroduce a divergence. + */ +export const NOT_ACKED_PREFIX_ROW = sql`${inboxEntries.status} <> 'acked'`; + /** * Commit inbox progress through the supplied entry id from the WS data plane, * scoped to the inboxes the connected socket has bound. @@ -585,6 +628,15 @@ async function collectPrecedingContext( * prefix are atomically marked `acked`; non-committable gaps reject the commit * so the database cannot persist `A pending, B acked`. * + * **Delta scan.** The prefix scan reads only rows that are not yet `acked`. + * Already-acked rows contribute nothing to any of the three decisions made + * below — they never trigger a gap, are never committable, and are never + * reset-from-pending — so excluding them in SQL is an exact equivalence, not + * an approximation. This is what keeps per-ACK cost proportional to the + * in-flight window instead of to chat history, and it holds only because + * `acked` is terminal: `recoverUnackedForScope` and `resetDeliveredForInboxes` + * reset `delivered` rows, never `acked` ones. + * * Trusts only the `inboxId` set the connected socket has bound (no `inboxId` * on the wire), and short-circuits on an empty `inboxIds`. */ @@ -607,27 +659,41 @@ export async function ackThroughEntryIdForBoundAgents( const chatPredicate = entry.chatId === null ? isNull(inboxEntries.chatId) : eq(inboxEntries.chatId, entry.chatId); const prefixRows = await tx - .select() + .select({ id: inboxEntries.id, status: inboxEntries.status, deliveredAt: inboxEntries.deliveredAt }) .from(inboxEntries) .where( and( eq(inboxEntries.inboxId, entry.inboxId), chatPredicate, eq(inboxEntries.notify, true), + NOT_ACKED_PREFIX_ROW, sql`${inboxEntries.id} <= ${entryId}`, ), ) .orderBy(asc(inboxEntries.id)) .for("update"); - const isResetDeliveredRow = (row: ClaimedEntry): boolean => row.status === "pending" && row.deliveredAt !== null; + const isResetDeliveredRow = (row: AckPrefixRow): boolean => row.status === "pending" && row.deliveredAt !== null; + // `status !== "acked"` is unreachable now that the SQL excludes acked + // rows, and is kept only as defensive redundancy — it is not what + // protects legacy out-of-enum rows. That protection lives in the SQL + // being an exclusion rather than an allow-list; see NOT_ACKED_PREFIX_ROW. if (prefixRows.some((row) => row.status !== "acked" && row.status !== "delivered" && !isResetDeliveredRow(row))) { return { ok: false, reason: "prefix_gap" }; } - const deliveredIds = prefixRows.filter((row) => row.status === "delivered").map((row) => row.id); - const resetPendingIds = prefixRows.filter(isResetDeliveredRow).map((row) => row.id); - const committableIds = [...deliveredIds, ...resetPendingIds]; + // Single pass over the id-ordered prefix, so `committableIds` is + // ascending by construction rather than by two concatenated filters. + const committableIds: number[] = []; + let resetFromPendingCount = 0; + for (const row of prefixRows) { + if (row.status === "delivered") { + committableIds.push(row.id); + } else if (isResetDeliveredRow(row)) { + committableIds.push(row.id); + resetFromPendingCount++; + } + } const ackedAt = new Date(); const drainPendingSilentRows = async (): Promise => { await tx @@ -661,11 +727,15 @@ export async function ackThroughEntryIdForBoundAgents( .where(and(inArray(inboxEntries.id, committableIds), inArray(inboxEntries.status, ["delivered", "pending"]))) .returning(); await drainPendingSilentRows(); + // UPDATE ... RETURNING has no defined row order, so sort explicitly: + // the WS layer treats `ackedEntryIds` as an ascending cursor list when + // it clears same-socket in-flight accounting. + updated.sort((a, b) => a.id - b.id); const updatedThroughEntry = updated.find((row) => row.id === entryId) ?? entry; return { ok: true, throughEntry: updatedThroughEntry, - disposition: resetPendingIds.length > 0 ? "accepted_from_pending" : "acked", + disposition: resetFromPendingCount > 0 ? "accepted_from_pending" : "acked", ackedCount: updated.length, ackedEntryIds: updated.map((row) => row.id), };