diff --git a/apps/cloud/drizzle/0007_powerful_dagger.sql b/apps/cloud/drizzle/0007_powerful_dagger.sql new file mode 100644 index 000000000..f0df09523 --- /dev/null +++ b/apps/cloud/drizzle/0007_powerful_dagger.sql @@ -0,0 +1,5 @@ +CREATE TABLE "identity_sync_cursors" ( + "provider" text PRIMARY KEY NOT NULL, + "cursor" text, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); diff --git a/apps/cloud/drizzle/meta/0007_snapshot.json b/apps/cloud/drizzle/meta/0007_snapshot.json new file mode 100644 index 000000000..48d822ca5 --- /dev/null +++ b/apps/cloud/drizzle/meta/0007_snapshot.json @@ -0,0 +1,1758 @@ +{ + "id": "88635579-52f4-4171-8ec5-105cba5f060e", + "prevId": "f8ad8544-f5eb-4857-9218-6dfde713c46e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identity_provider": { + "name": "identity_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workos'" + }, + "updated_at": { + "name": "updated_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()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identity_sync_cursors": { + "name": "identity_sync_cursors", + "schema": "", + "columns": { + "provider": { + "name": "provider", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.identity_sync_events": { + "name": "identity_sync_events", + "schema": "", + "columns": { + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "identity_sync_events_provider_event_id_pk": { + "name": "identity_sync_events_provider_event_id_pk", + "columns": [ + "provider", + "event_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memberships": { + "name": "memberships", + "schema": "", + "columns": { + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identity_provider": { + "name": "identity_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workos'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "role_slug": { + "name": "role_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "synced_at": { + "name": "synced_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()" + } + }, + "indexes": { + "memberships_organization_id_idx": { + "name": "memberships_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memberships_provider_external_id_idx": { + "name": "memberships_provider_external_id_idx", + "columns": [ + { + "expression": "identity_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memberships_account_id_accounts_id_fk": { + "name": "memberships_account_id_accounts_id_fk", + "tableFrom": "memberships", + "tableTo": "accounts", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "memberships_organization_id_organizations_id_fk": { + "name": "memberships_organization_id_organizations_id_fk", + "tableFrom": "memberships", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "memberships_account_id_organization_id_pk": { + "name": "memberships_account_id_organization_id_pk", + "columns": [ + "account_id", + "organization_id" + ] + } + }, + "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 + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "identity_provider": { + "name": "identity_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workos'" + }, + "updated_at": { + "name": "updated_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()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.blob": { + "name": "blob", + "schema": "", + "columns": { + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "blob_namespace_key_pk": { + "name": "blob_namespace_key_pk", + "columns": [ + "namespace", + "key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection": { + "name": "connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identity_label": { + "name": "identity_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_secret_id": { + "name": "access_token_secret_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token_secret_id": { + "name": "refresh_token_secret_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_state": { + "name": "provider_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "connection_scope_id_idx": { + "name": "connection_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_provider_idx": { + "name": "connection_provider_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "connection_scope_id_id_pk": { + "name": "connection_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.definition": { + "name": "definition", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "definition_scope_id_idx": { + "name": "definition_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "definition_source_id_idx": { + "name": "definition_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "definition_plugin_id_idx": { + "name": "definition_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "definition_scope_id_id_pk": { + "name": "definition_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.graphql_operation": { + "name": "graphql_operation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "binding": { + "name": "binding", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "graphql_operation_scope_id_idx": { + "name": "graphql_operation_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "graphql_operation_source_id_idx": { + "name": "graphql_operation_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "graphql_operation_scope_id_id_pk": { + "name": "graphql_operation_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.graphql_source": { + "name": "graphql_source", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "graphql_source_scope_id_idx": { + "name": "graphql_source_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "graphql_source_scope_id_id_pk": { + "name": "graphql_source_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_binding": { + "name": "mcp_binding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "binding": { + "name": "binding", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "mcp_binding_scope_id_idx": { + "name": "mcp_binding_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_binding_source_id_idx": { + "name": "mcp_binding_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "mcp_binding_scope_id_id_pk": { + "name": "mcp_binding_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_oauth_session": { + "name": "mcp_oauth_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session": { + "name": "session", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "mcp_oauth_session_scope_id_idx": { + "name": "mcp_oauth_session_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "mcp_oauth_session_scope_id_id_pk": { + "name": "mcp_oauth_session_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_source": { + "name": "mcp_source", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "mcp_source_scope_id_idx": { + "name": "mcp_source_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "mcp_source_scope_id_id_pk": { + "name": "mcp_source_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.openapi_oauth_session": { + "name": "openapi_oauth_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session": { + "name": "session", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "openapi_oauth_session_scope_id_idx": { + "name": "openapi_oauth_session_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "openapi_oauth_session_scope_id_id_pk": { + "name": "openapi_oauth_session_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.openapi_operation": { + "name": "openapi_operation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "binding": { + "name": "binding", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "openapi_operation_scope_id_idx": { + "name": "openapi_operation_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "openapi_operation_source_id_idx": { + "name": "openapi_operation_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "openapi_operation_scope_id_id_pk": { + "name": "openapi_operation_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.openapi_source": { + "name": "openapi_source", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "spec": { + "name": "spec", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "oauth2": { + "name": "oauth2", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "invocation_config": { + "name": "invocation_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "openapi_source_scope_id_idx": { + "name": "openapi_source_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "openapi_source_scope_id_id_pk": { + "name": "openapi_source_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.openapi_source_binding": { + "name": "openapi_source_binding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_scope_id": { + "name": "source_scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_scope_id": { + "name": "target_scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "openapi_source_binding_source_id_idx": { + "name": "openapi_source_binding_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "openapi_source_binding_source_scope_id_idx": { + "name": "openapi_source_binding_source_scope_id_idx", + "columns": [ + { + "expression": "source_scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "openapi_source_binding_target_scope_id_idx": { + "name": "openapi_source_binding_target_scope_id_idx", + "columns": [ + { + "expression": "target_scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "openapi_source_binding_slot_idx": { + "name": "openapi_source_binding_slot_idx", + "columns": [ + { + "expression": "slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "openapi_source_binding_id_pk": { + "name": "openapi_source_binding_id_pk", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret": { + "name": "secret", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owned_by_connection_id": { + "name": "owned_by_connection_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "secret_scope_id_idx": { + "name": "secret_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_provider_idx": { + "name": "secret_provider_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_owned_by_connection_id_idx": { + "name": "secret_owned_by_connection_id_idx", + "columns": [ + { + "expression": "owned_by_connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "secret_scope_id_id_pk": { + "name": "secret_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.source": { + "name": "source", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "can_remove": { + "name": "can_remove", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "can_refresh": { + "name": "can_refresh", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "can_edit": { + "name": "can_edit", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "source_scope_id_idx": { + "name": "source_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "source_plugin_id_idx": { + "name": "source_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "source_scope_id_id_pk": { + "name": "source_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool": { + "name": "tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "output_schema": { + "name": "output_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "tool_scope_id_idx": { + "name": "tool_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_source_id_idx": { + "name": "tool_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_plugin_id_idx": { + "name": "tool_plugin_id_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tool_scope_id_id_pk": { + "name": "tool_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workos_vault_metadata": { + "name": "workos_vault_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "workos_vault_metadata_scope_id_idx": { + "name": "workos_vault_metadata_scope_id_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "workos_vault_metadata_scope_id_id_pk": { + "name": "workos_vault_metadata_scope_id_id_pk", + "columns": [ + "scope_id", + "id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/cloud/drizzle/meta/_journal.json b/apps/cloud/drizzle/meta/_journal.json index 5075ad647..0a5d3c7cd 100644 --- a/apps/cloud/drizzle/meta/_journal.json +++ b/apps/cloud/drizzle/meta/_journal.json @@ -50,6 +50,13 @@ "when": 1777397349159, "tag": "0006_polite_power_man", "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1777397903262, + "tag": "0007_powerful_dagger", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/cloud/src/api/layers.ts b/apps/cloud/src/api/layers.ts index 042879dbb..fe4032097 100644 --- a/apps/cloud/src/api/layers.ts +++ b/apps/cloud/src/api/layers.ts @@ -22,6 +22,7 @@ import { OrgHttpApi } from "../org/compose"; import { OrgHandlers } from "../org/handlers"; import { IdentityApi, IdentityWebhookHandlers } from "../identity/handlers"; import { IdentitySync } from "../identity/sync"; +import { IdentityReconciliation } from "../identity/reconciliation"; import { ErrorCaptureLive } from "../observability"; import { CoreSharedServices } from "./core-shared-services"; @@ -46,12 +47,18 @@ const IdentitySyncLive = IdentitySync.Live.pipe( Layer.provideMerge(UserStoreLive), Layer.provideMerge(CoreSharedServices), ); +const IdentityReconciliationLive = IdentityReconciliation.Live.pipe( + Layer.provideMerge(UserStoreLive), + Layer.provideMerge(IdentitySyncLive), + Layer.provideMerge(CoreSharedServices), +); export const SharedServices = Layer.mergeAll( DbLive, UserStoreLive, IdentityDirectoryLive, IdentitySyncLive, + IdentityReconciliationLive, CoreSharedServices, HttpServer.layerContext, TelemetryLive, diff --git a/apps/cloud/src/auth/workos.ts b/apps/cloud/src/auth/workos.ts index 8ae18ed01..090698493 100644 --- a/apps/cloud/src/auth/workos.ts +++ b/apps/cloud/src/auth/workos.ts @@ -224,6 +224,31 @@ const make = Effect.gen(function* () { secret, }), ), + + /** Pull identity-related WorkOS events for replay/reconciliation. */ + listIdentityEvents: (options: { + after?: string; + rangeStart?: string; + limit?: number; + order?: "asc" | "desc"; + }) => + use((wos) => + wos.events.listEvents({ + events: [ + "user.created", + "user.updated", + "organization.created", + "organization.updated", + "organization_membership.created", + "organization_membership.updated", + "organization_membership.deleted", + ], + ...(options.after ? { after: options.after } : {}), + ...(options.rangeStart ? { rangeStart: options.rangeStart } : {}), + ...(options.limit ? { limit: options.limit } : {}), + order: options.order ?? "asc", + }), + ), }; }); diff --git a/apps/cloud/src/identity/reconciliation.test.ts b/apps/cloud/src/identity/reconciliation.test.ts new file mode 100644 index 000000000..6c476651f --- /dev/null +++ b/apps/cloud/src/identity/reconciliation.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, it } from "@effect/vitest"; +import { Effect, Layer } from "effect"; + +import { UserStoreService } from "../auth/context"; +import { WorkOSAuth } from "../auth/workos"; +import { IdentityReconciliation } from "./reconciliation"; +import { IdentitySync } from "./sync"; + +const makeReconciliation = () => { + let cursor: string | null = "cursor_0"; + const applied: string[] = []; + const listCalls: Array<{ after?: string; rangeStart?: string }> = []; + + const UserStoreTest = Layer.succeed(UserStoreService, { + use: (fn: (store: { + getIdentityCursor: (provider: string) => Promise; + setIdentityCursor: (provider: string, cursor: string | null) => Promise; + }) => Promise) => + Effect.promise(() => + fn({ + getIdentityCursor: async () => cursor, + setIdentityCursor: async (_provider, next) => { + cursor = next; + return { provider: "workos", cursor: next }; + }, + }), + ), + } as unknown as UserStoreService["Type"]); + + const WorkOSTest = Layer.succeed(WorkOSAuth, { + listIdentityEvents: (options: { after?: string; rangeStart?: string }) => + Effect.sync(() => { + listCalls.push(options); + return { + data: [ + { id: "event_1", event: "user.created", data: { id: "user_1" } }, + { id: "event_2", event: "unknown.event", data: {} }, + ], + listMetadata: { after: "cursor_1" }, + }; + }), + } as unknown as WorkOSAuth["Type"]); + + const IdentitySyncTest = Layer.succeed(IdentitySync, { + applyEvent: (event: { id: string; event: string }) => + Effect.sync(() => { + applied.push(event.id); + return event.event === "unknown.event" ? "ignored" : "processed"; + }), + constructAndApplyWebhook: () => Effect.succeed("ignored" as const), + } as IdentitySync["Type"]); + + return { + get cursor() { + return cursor; + }, + applied, + listCalls, + layer: IdentityReconciliation.Live.pipe( + Layer.provideMerge(UserStoreTest), + Layer.provideMerge(WorkOSTest), + Layer.provideMerge(IdentitySyncTest), + ) as Layer.Layer, + }; +}; + +describe("IdentityReconciliation", () => { + it.effect("pulls after the stored cursor and advances it", () => + Effect.gen(function* () { + const reconciliation = makeReconciliation(); + + const result = yield* Effect.provide( + Effect.gen(function* () { + const service = yield* IdentityReconciliation; + return yield* service.replayWorkOSEvents(); + }), + reconciliation.layer, + ); + + expect(reconciliation.listCalls).toEqual([ + { after: "cursor_0", rangeStart: undefined, limit: 100, order: "asc" }, + ]); + expect(reconciliation.applied).toEqual(["event_1", "event_2"]); + expect(reconciliation.cursor).toBe("cursor_1"); + expect(result).toEqual({ + processed: 1, + duplicate: 0, + ignored: 1, + cursor: "cursor_1", + }); + }), + ); + + it.effect("uses rangeStart instead of stored cursor for explicit replay", () => + Effect.gen(function* () { + const reconciliation = makeReconciliation(); + + yield* Effect.provide( + Effect.gen(function* () { + const service = yield* IdentityReconciliation; + return yield* service.replayWorkOSEvents({ rangeStart: "2026-01-01T00:00:00Z" }); + }), + reconciliation.layer, + ); + + expect(reconciliation.listCalls[0]).toEqual({ + after: undefined, + rangeStart: "2026-01-01T00:00:00Z", + limit: 100, + order: "asc", + }); + }), + ); +}); diff --git a/apps/cloud/src/identity/reconciliation.ts b/apps/cloud/src/identity/reconciliation.ts new file mode 100644 index 000000000..6e6521b8a --- /dev/null +++ b/apps/cloud/src/identity/reconciliation.ts @@ -0,0 +1,72 @@ +import { Context, Effect, Layer } from "effect"; + +import { UserStoreService } from "../auth/context"; +import { WorkOSError, type UserStoreError } from "../auth/errors"; +import { WorkOSAuth } from "../auth/workos"; +import { IdentitySync } from "./sync"; + +export type IdentityReconcileResult = { + readonly processed: number; + readonly duplicate: number; + readonly ignored: number; + readonly cursor: string | null; +}; + +export class IdentityReconciliation extends Context.Tag( + "@executor/cloud/IdentityReconciliation", +)< + IdentityReconciliation, + { + readonly replayWorkOSEvents: (options?: { + readonly rangeStart?: string; + readonly limit?: number; + }) => Effect.Effect; + } +>() { + static Live = Layer.effect( + this, + Effect.gen(function* () { + const users = yield* UserStoreService; + const workos = yield* WorkOSAuth; + const sync = yield* IdentitySync; + + return IdentityReconciliation.of({ + replayWorkOSEvents: (options = {}) => + Effect.gen(function* () { + const after = + options.rangeStart == null + ? yield* users.use((store) => store.getIdentityCursor("workos")) + : null; + const result = yield* workos.listIdentityEvents({ + after: after ?? undefined, + rangeStart: options.rangeStart, + limit: options.limit ?? 100, + order: "asc", + }); + + let processed = 0; + let duplicate = 0; + let ignored = 0; + + for (const event of result.data) { + const status = yield* sync.applyEvent({ + id: String(event.id), + event: event.event, + data: event.data as Record, + }); + if (status === "processed") processed++; + if (status === "duplicate") duplicate++; + if (status === "ignored") ignored++; + } + + const cursor = result.listMetadata.after ?? after; + if (cursor) { + yield* users.use((store) => store.setIdentityCursor("workos", cursor)); + } + + return { processed, duplicate, ignored, cursor }; + }), + }); + }), + ); +} diff --git a/apps/cloud/src/services/schema.ts b/apps/cloud/src/services/schema.ts index daf8dfe04..127f7a61e 100644 --- a/apps/cloud/src/services/schema.ts +++ b/apps/cloud/src/services/schema.ts @@ -76,3 +76,9 @@ export const identitySyncEvents = pgTable( pk: primaryKey({ columns: [t.provider, t.eventId] }), }), ); + +export const identitySyncCursors = pgTable("identity_sync_cursors", { + provider: text("provider").primaryKey(), + cursor: text("cursor"), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), +}); diff --git a/apps/cloud/src/services/user-store.ts b/apps/cloud/src/services/user-store.ts index 39fd958d6..8141984b5 100644 --- a/apps/cloud/src/services/user-store.ts +++ b/apps/cloud/src/services/user-store.ts @@ -9,7 +9,13 @@ import { and, eq } from "drizzle-orm"; -import { accounts, identitySyncEvents, memberships, organizations } from "./schema"; +import { + accounts, + identitySyncCursors, + identitySyncEvents, + memberships, + organizations, +} from "./schema"; import type { DrizzleDb } from "./db"; export type Account = typeof accounts.$inferSelect; @@ -176,4 +182,24 @@ export const makeUserStore = (db: DrizzleDb) => ({ .returning(); return result != null; }, + + getIdentityCursor: async (provider: string) => { + const rows = await db + .select() + .from(identitySyncCursors) + .where(eq(identitySyncCursors.provider, provider)); + return rows[0]?.cursor ?? null; + }, + + setIdentityCursor: async (provider: string, cursor: string | null) => { + const [result] = await db + .insert(identitySyncCursors) + .values({ provider, cursor }) + .onConflictDoUpdate({ + target: identitySyncCursors.provider, + set: { cursor, updatedAt: new Date() }, + }) + .returning(); + return result!; + }, });