use sqlx::{PgPool, query}; pub async fn apply_postgres(pool: &PgPool) -> Result<(), sqlx::Error> { query( "create table if not exists workspaces ( id text primary key, slug text not null unique, display_name text not null, status text not null, settings_json jsonb not null default '{}'::jsonb, created_at timestamptz not null, updated_at timestamptz not null )", ) .execute(pool) .await?; query( "create table if not exists users ( id text primary key, email text not null unique, display_name text not null, password_hash text null, status text not null, created_at timestamptz not null )", ) .execute(pool) .await?; query("alter table users add column if not exists password_hash text null") .execute(pool) .await?; query( "insert into users ( id, email, display_name, status, created_at ) values ( 'user_default_owner', 'owner@crank.local', 'Workspace Owner', 'active', now() ) on conflict (id) do nothing", ) .execute(pool) .await?; query( "create table if not exists memberships ( workspace_id text not null references workspaces(id) on delete cascade, user_id text not null references users(id) on delete cascade, role text not null, created_at timestamptz not null, primary key (workspace_id, user_id) )", ) .execute(pool) .await?; query( "create table if not exists user_sessions ( id text primary key, user_id text not null references users(id) on delete cascade, current_workspace_id text null references workspaces(id) on delete set null, secret_hash text not null, status text not null, expires_at timestamptz not null, last_seen_at timestamptz null, created_at timestamptz not null )", ) .execute(pool) .await?; query( "alter table user_sessions add column if not exists current_workspace_id text null references workspaces(id) on delete set null", ) .execute(pool) .await?; query( "insert into workspaces ( id, slug, display_name, status, settings_json, created_at, updated_at ) values ( 'ws_default', 'default', 'Default Workspace', 'active', '{}'::jsonb, now(), now() ) on conflict (id) do nothing", ) .execute(pool) .await?; query( "insert into memberships ( workspace_id, user_id, role, created_at ) values ( 'ws_default', 'user_default_owner', 'owner', now() ) on conflict (workspace_id, user_id) do nothing", ) .execute(pool) .await?; query( "create table if not exists invitation_tokens ( id text primary key, workspace_id text not null references workspaces(id) on delete cascade, email text not null, role text not null, status text not null, token_hash text not null, expires_at timestamptz not null, created_at timestamptz not null )", ) .execute(pool) .await?; query( "create table if not exists platform_api_keys ( id text primary key, workspace_id text not null references workspaces(id) on delete cascade, agent_id text null, name text not null, prefix text not null, secret_hash text not null, scopes_json jsonb not null, status text not null, created_at timestamptz not null, last_used_at timestamptz null, revoked_at timestamptz null )", ) .execute(pool) .await?; query( "create unique index if not exists platform_api_keys_workspace_name_idx on platform_api_keys(workspace_id, name)", ) .execute(pool) .await?; query("alter table platform_api_keys add column if not exists agent_id text null") .execute(pool) .await?; query( "insert into workspaces ( id, slug, display_name, status, settings_json, created_at, updated_at ) values ( 'ws_default', 'default', 'Default Workspace', 'active', '{}'::jsonb, now(), now() ) on conflict (id) do nothing", ) .execute(pool) .await?; query( "create table if not exists operations ( id text primary key, workspace_id text null references workspaces(id) on delete cascade, name text not null, display_name text not null, category text not null default 'general', protocol text not null, security_level text not null default 'standard', status text not null, current_draft_version integer not null default 1, latest_published_version integer null, created_at timestamptz not null, updated_at timestamptz not null, published_at timestamptz null )", ) .execute(pool) .await?; query("alter table operations add column if not exists workspace_id text null references workspaces(id) on delete cascade") .execute(pool) .await?; query( "alter table operations add column if not exists category text not null default 'general'", ) .execute(pool) .await?; query( "alter table operations add column if not exists security_level text not null default 'standard'", ) .execute(pool) .await?; query("update operations set workspace_id = 'ws_default' where workspace_id is null") .execute(pool) .await?; query("alter table operations alter column workspace_id set not null") .execute(pool) .await?; query("alter table operations drop constraint if exists operations_name_key") .execute(pool) .await?; query( "create unique index if not exists operations_workspace_name_idx on operations(workspace_id, name)", ) .execute(pool) .await?; query( "create table if not exists operation_versions ( operation_id text not null references operations(id) on delete cascade, version integer not null, status text not null, target_json jsonb not null, input_schema_json jsonb not null, output_schema_json jsonb not null, input_mapping_json jsonb not null, output_mapping_json jsonb not null, execution_config_json jsonb not null, tool_description_json jsonb not null, samples_json jsonb null, generated_draft_json jsonb null, config_export_json jsonb null, change_note text null, created_at timestamptz not null, created_by text null, primary key (operation_id, version) )", ) .execute(pool) .await?; query( "create table if not exists published_operations ( operation_id text primary key references operations(id) on delete cascade, version integer not null, published_at timestamptz not null, published_by text null, foreign key (operation_id, version) references operation_versions(operation_id, version) on delete cascade )", ) .execute(pool) .await?; query( "create table if not exists operation_samples ( id text primary key, operation_id text not null references operations(id) on delete cascade, version integer not null, sample_kind text not null, storage_ref text not null, content_type text not null, file_name text null, created_at timestamptz not null, foreign key (operation_id, version) references operation_versions(operation_id, version) on delete cascade )", ) .execute(pool) .await?; query( "create table if not exists descriptors ( id text primary key, operation_id text null references operations(id) on delete cascade, version integer null, descriptor_kind text not null, storage_ref text not null, source_name text null, package_index_json jsonb null, created_at timestamptz not null, foreign key (operation_id, version) references operation_versions(operation_id, version) on delete cascade )", ) .execute(pool) .await?; query( "create table if not exists auth_profiles ( id text primary key, workspace_id text null references workspaces(id) on delete cascade, name text not null, kind text not null, config_json jsonb not null, created_at timestamptz not null, updated_at timestamptz not null )", ) .execute(pool) .await?; query("alter table auth_profiles add column if not exists workspace_id text null references workspaces(id) on delete cascade") .execute(pool) .await?; query("update auth_profiles set workspace_id = 'ws_default' where workspace_id is null") .execute(pool) .await?; query("alter table auth_profiles alter column workspace_id set not null") .execute(pool) .await?; query("alter table auth_profiles drop constraint if exists auth_profiles_name_key") .execute(pool) .await?; query( "create unique index if not exists auth_profiles_workspace_name_idx on auth_profiles(workspace_id, name)", ) .execute(pool) .await?; query( "create table if not exists secrets ( id text primary key, workspace_id text not null references workspaces(id) on delete cascade, name text not null, kind text not null, status text not null, current_version integer not null, last_used_at timestamptz null, created_at timestamptz not null, updated_at timestamptz not null )", ) .execute(pool) .await?; query( "create unique index if not exists secrets_workspace_name_idx on secrets(workspace_id, name)", ) .execute(pool) .await?; query( "create table if not exists secret_versions ( secret_id text not null references secrets(id) on delete cascade, version integer not null, ciphertext text not null, key_version text not null, created_at timestamptz not null, created_by text null references users(id) on delete set null, primary key (secret_id, version) )", ) .execute(pool) .await?; query( "create table if not exists yaml_import_jobs ( id text primary key, source_sample_id text null references operation_samples(id) on delete set null, status text not null, format_version text not null, mode text not null, result_operation_id text null references operations(id) on delete set null, result_version integer null, error_text text null, created_at timestamptz not null, finished_at timestamptz null )", ) .execute(pool) .await?; query( "create table if not exists agents ( id text primary key, workspace_id text not null references workspaces(id) on delete cascade, slug text not null, display_name text not null, description text not null, status text not null, current_draft_version integer not null default 1, latest_published_version integer null, created_at timestamptz not null, updated_at timestamptz not null, published_at timestamptz null )", ) .execute(pool) .await?; query( "create unique index if not exists agents_workspace_slug_idx on agents(workspace_id, slug)", ) .execute(pool) .await?; query( "create table if not exists agent_versions ( agent_id text not null references agents(id) on delete cascade, version integer not null, status text not null, instructions_json jsonb not null, tool_selection_policy_json jsonb not null, created_at timestamptz not null, primary key (agent_id, version) )", ) .execute(pool) .await?; query( "create table if not exists agent_operation_bindings ( agent_id text not null references agents(id) on delete cascade, agent_version integer not null, operation_id text not null references operations(id) on delete cascade, operation_version integer not null, tool_name text not null, tool_title text not null, tool_description_override text null, enabled boolean not null default true, foreign key (agent_id, agent_version) references agent_versions(agent_id, version) on delete cascade, foreign key (operation_id, operation_version) references operation_versions(operation_id, version) on delete cascade )", ) .execute(pool) .await?; query( "create unique index if not exists agent_bindings_tool_name_idx on agent_operation_bindings(agent_id, agent_version, tool_name)", ) .execute(pool) .await?; query( "create table if not exists published_agents ( agent_id text primary key references agents(id) on delete cascade, version integer not null, published_at timestamptz not null, published_by text null, foreign key (agent_id, version) references agent_versions(agent_id, version) on delete cascade )", ) .execute(pool) .await?; query( "create table if not exists invocation_logs ( id text primary key, workspace_id text not null references workspaces(id) on delete cascade, agent_id text null references agents(id) on delete set null, operation_id text not null references operations(id) on delete cascade, source text not null, level text not null, status text not null, tool_name text not null, message text not null, request_id text null, status_code integer null, duration_ms bigint not null, error_kind text null, request_preview_json jsonb not null, response_preview_json jsonb not null, created_at timestamptz not null )", ) .execute(pool) .await?; query( "create index if not exists invocation_logs_workspace_created_idx on invocation_logs(workspace_id, created_at desc)", ) .execute(pool) .await?; query( "create index if not exists invocation_logs_workspace_operation_created_idx on invocation_logs(workspace_id, operation_id, created_at desc)", ) .execute(pool) .await?; query( "create index if not exists invocation_logs_workspace_agent_created_idx on invocation_logs(workspace_id, agent_id, created_at desc)", ) .execute(pool) .await?; query( "create table if not exists usage_rollups ( workspace_id text not null references workspaces(id) on delete cascade, agent_id text null references agents(id) on delete cascade, operation_id text null references operations(id) on delete cascade, period text not null, calls_total bigint not null, calls_ok bigint not null, calls_error bigint not null, p50_ms bigint not null, p95_ms bigint not null, p99_ms bigint not null, updated_at timestamptz not null )", ) .execute(pool) .await?; query( "create table if not exists stream_sessions ( id text primary key, workspace_id text not null references workspaces(id) on delete cascade, agent_id text null references agents(id) on delete set null, operation_id text not null references operations(id) on delete cascade, protocol text not null, mode text not null, status text not null, cursor_json jsonb null, state_json jsonb not null, expires_at timestamptz not null, last_poll_at timestamptz null, created_at timestamptz not null, closed_at timestamptz null )", ) .execute(pool) .await?; query( "create index if not exists stream_sessions_workspace_status_idx on stream_sessions(workspace_id, status, created_at desc)", ) .execute(pool) .await?; query( "create index if not exists stream_sessions_expires_at_idx on stream_sessions(expires_at)", ) .execute(pool) .await?; query( "create table if not exists async_jobs ( id text primary key, workspace_id text not null references workspaces(id) on delete cascade, agent_id text null references agents(id) on delete set null, operation_id text not null references operations(id) on delete cascade, status text not null, progress_json jsonb not null, result_json jsonb null, error_json jsonb null, expires_at timestamptz null, last_poll_at timestamptz null, created_at timestamptz not null, updated_at timestamptz not null, finished_at timestamptz null )", ) .execute(pool) .await?; query("alter table async_jobs add column if not exists last_poll_at timestamptz null") .execute(pool) .await?; query( "create index if not exists async_jobs_workspace_status_idx on async_jobs(workspace_id, status, updated_at desc)", ) .execute(pool) .await?; query( "create index if not exists async_jobs_expires_at_idx on async_jobs(expires_at)", ) .execute(pool) .await?; Ok(()) }