step 4: fresh kernel-only migrations
Deleted all 23+23 old chat-switchboard migration files. Wrote 9+9 clean kernel-only migrations in postgres/ and sqlite/ subdirs. 27 tables per dialect covering all 20 store interfaces: 001: users, refresh_tokens, policies, settings, presence, oidc 002: teams, team_members, groups, group_members 003: packages, package_user_settings, ext_permissions, ext_data_tables 004: ext_connections, ext_dependencies, resource_grants 005: notifications, notification_preferences 006: audit_log 007: workflows, workflow_stages, workflow_versions 008: tasks, task_runs 009: ws_tickets, rate_limit_counters Dropped: providers, personas, channels, messages, knowledge, notes, memory, workspaces, projects, folders, files, usage_log, tool_health, workflow_assignments, ext_view_channels. Schema changes vs old: - pgvector extension removed (no KB embeddings) - resource_grants: resource_type CHECK removed (open-ended) - workflow_stages: persona_id kept as nullable TEXT (no FK) - workflow_stages: stage_mode default=form_only, added custom - tasks: dropped output_channel_id, provider_config_id columns - tasks: task_type removed prompt, output_mode: notification|webhook|log - task_runs: dropped channel_id - platform_policies: kernel-only seeds - global_settings: kernel-only seeds, site name=Switchboard Core - Everyone group: kernel permissions (extension.use, workflow.submit) -2815/+487 lines.
This commit is contained in:
118
server/database/migrations/postgres/001_core.sql
Normal file
118
server/database/migrations/postgres/001_core.sql
Normal file
@@ -0,0 +1,118 @@
|
||||
-- ==========================================
|
||||
-- Switchboard Core — 001 Core
|
||||
-- ==========================================
|
||||
-- Users, auth, platform policies, global settings, presence, OIDC.
|
||||
-- ==========================================
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- ── Users ───────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username VARCHAR(50) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
password_hash TEXT,
|
||||
display_name VARCHAR(100),
|
||||
avatar_url TEXT,
|
||||
role VARCHAR(20) DEFAULT 'user'
|
||||
CHECK (role IN ('user', 'admin')),
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
settings JSONB DEFAULT '{}'::jsonb,
|
||||
auth_source VARCHAR(20) NOT NULL DEFAULT 'builtin'
|
||||
CHECK (auth_source IN ('builtin', 'mtls', 'oidc')),
|
||||
external_id TEXT,
|
||||
handle VARCHAR(100) NOT NULL,
|
||||
encrypted_uek BYTEA,
|
||||
uek_salt BYTEA,
|
||||
uek_nonce BYTEA,
|
||||
vault_set BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
last_login_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS users_username_ci ON users (LOWER(username));
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS users_email_ci ON users (LOWER(email));
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_handle ON users(LOWER(handle));
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_external_id
|
||||
ON users(auth_source, external_id) WHERE external_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
|
||||
DROP TRIGGER IF EXISTS users_updated_at ON users;
|
||||
CREATE TRIGGER users_updated_at BEFORE UPDATE ON users
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
|
||||
-- ── Refresh Tokens ──────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
token_hash TEXT NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
revoked_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user ON refresh_tokens(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash ON refresh_tokens(token_hash) WHERE revoked_at IS NULL;
|
||||
|
||||
-- ── Platform Policies ───────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS platform_policies (
|
||||
key VARCHAR(50) PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_by UUID REFERENCES users(id),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
INSERT INTO platform_policies (key, value) VALUES
|
||||
('allow_registration', 'true'),
|
||||
('default_user_active', 'false')
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- ── Global Settings ─────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS global_settings (
|
||||
key VARCHAR(100) PRIMARY KEY,
|
||||
value JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_by UUID REFERENCES users(id)
|
||||
);
|
||||
|
||||
INSERT INTO global_settings (key, value) VALUES
|
||||
('registration', '{"enabled": true}'::jsonb),
|
||||
('site', '{"name": "Switchboard Core", "tagline": "Extension Platform"}'::jsonb),
|
||||
('banner', '{"enabled": false, "text": "", "bg": "#007a33", "fg": "#ffffff"}'::jsonb)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
-- ── User Presence ───────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_presence (
|
||||
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
||||
last_seen TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'online'
|
||||
CHECK (status IN ('online', 'away', 'offline'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_presence_last_seen ON user_presence(last_seen DESC);
|
||||
|
||||
-- ── OIDC Auth State ─────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS oidc_auth_state (
|
||||
state TEXT PRIMARY KEY,
|
||||
nonce TEXT NOT NULL,
|
||||
redirect_to TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_oidc_state_created ON oidc_auth_state(created_at);
|
||||
85
server/database/migrations/postgres/002_teams.sql
Normal file
85
server/database/migrations/postgres/002_teams.sql
Normal file
@@ -0,0 +1,85 @@
|
||||
-- ==========================================
|
||||
-- Switchboard Core — 002 Teams & Access Control
|
||||
-- ==========================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS teams (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(200) NOT NULL UNIQUE,
|
||||
description TEXT DEFAULT '',
|
||||
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
settings JSONB DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_teams_active ON teams(is_active) WHERE is_active = true;
|
||||
DROP TRIGGER IF EXISTS teams_updated_at ON teams;
|
||||
CREATE TRIGGER teams_updated_at BEFORE UPDATE ON teams
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS team_members (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role VARCHAR(20) NOT NULL DEFAULT 'member'
|
||||
CHECK (role IN ('admin', 'member')),
|
||||
joined_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(team_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_team_members_team ON team_members(team_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_team_members_user ON team_members(user_id);
|
||||
|
||||
-- ── Groups ──────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(100) NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
scope VARCHAR(20) NOT NULL DEFAULT 'global'
|
||||
CHECK (scope IN ('global', 'team')),
|
||||
team_id UUID REFERENCES teams(id) ON DELETE CASCADE,
|
||||
created_by UUID REFERENCES users(id),
|
||||
source VARCHAR(20) NOT NULL DEFAULT 'manual'
|
||||
CHECK (source IN ('manual', 'oidc', 'system')),
|
||||
permissions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
token_budget_daily BIGINT,
|
||||
token_budget_monthly BIGINT,
|
||||
allowed_models JSONB,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
CONSTRAINT groups_scope_team CHECK (
|
||||
(scope = 'global' AND team_id IS NULL) OR
|
||||
(scope = 'team' AND team_id IS NOT NULL)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_groups_name_scope
|
||||
ON groups(name, COALESCE(team_id, '00000000-0000-0000-0000-000000000000'));
|
||||
DROP TRIGGER IF EXISTS groups_updated_at ON groups;
|
||||
CREATE TRIGGER groups_updated_at BEFORE UPDATE ON groups
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS group_members (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
group_id UUID NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
added_by UUID NOT NULL REFERENCES users(id),
|
||||
added_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(group_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_group_members_group ON group_members(group_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_group_members_user ON group_members(user_id);
|
||||
|
||||
-- Seed Everyone group
|
||||
INSERT INTO groups (id, name, description, scope, created_by, source, permissions)
|
||||
VALUES (
|
||||
'00000000-0000-0000-0000-000000000001',
|
||||
'Everyone',
|
||||
'Implicit group — all authenticated users receive these permissions.',
|
||||
'global', NULL, 'system',
|
||||
'["extension.use","workflow.submit"]'::jsonb
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
78
server/database/migrations/postgres/003_packages.sql
Normal file
78
server/database/migrations/postgres/003_packages.sql
Normal file
@@ -0,0 +1,78 @@
|
||||
-- ==========================================
|
||||
-- Switchboard Core — 003 Packages & Extensions
|
||||
-- ==========================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS packages (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT 'surface'
|
||||
CHECK (type IN ('surface', 'extension', 'full', 'workflow', 'library')),
|
||||
version TEXT NOT NULL DEFAULT '0.0.0',
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
author TEXT NOT NULL DEFAULT '',
|
||||
tier TEXT NOT NULL DEFAULT 'browser'
|
||||
CHECK (tier IN ('browser', 'starlark', 'sidecar')),
|
||||
is_system BOOLEAN NOT NULL DEFAULT false,
|
||||
scope TEXT NOT NULL DEFAULT 'global'
|
||||
CHECK (scope IN ('global', 'team', 'personal')),
|
||||
team_id UUID REFERENCES teams(id) ON DELETE CASCADE,
|
||||
installed_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
manifest JSONB NOT NULL DEFAULT '{}',
|
||||
enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
status TEXT NOT NULL DEFAULT 'active'
|
||||
CHECK (status IN ('active', 'pending_review', 'suspended')),
|
||||
schema_version INTEGER NOT NULL DEFAULT 0,
|
||||
package_settings JSONB NOT NULL DEFAULT '{}',
|
||||
source TEXT NOT NULL DEFAULT 'core'
|
||||
CHECK (source IN ('core', 'builtin', 'extension', 'registry')),
|
||||
installed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_packages_type ON packages(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_packages_enabled ON packages(enabled) WHERE enabled = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_packages_team ON packages(team_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_packages_source ON packages(source);
|
||||
CREATE INDEX IF NOT EXISTS idx_packages_status ON packages(status);
|
||||
|
||||
-- ── Package User Settings ───────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS package_user_settings (
|
||||
package_id TEXT NOT NULL REFERENCES packages(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
settings JSONB NOT NULL DEFAULT '{}',
|
||||
is_enabled BOOLEAN NOT NULL DEFAULT true,
|
||||
PRIMARY KEY (package_id, user_id)
|
||||
);
|
||||
|
||||
-- ── Extension Permissions ───────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS extension_permissions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
package_id TEXT NOT NULL REFERENCES packages(id) ON DELETE CASCADE,
|
||||
permission TEXT NOT NULL,
|
||||
granted BOOLEAN NOT NULL DEFAULT false,
|
||||
granted_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
granted_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE(package_id, permission)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_perm_package ON extension_permissions(package_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_perm_granted ON extension_permissions(granted) WHERE granted = true;
|
||||
|
||||
-- ── Extension Data Tables Catalog ───────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ext_data_tables (
|
||||
package_id TEXT NOT NULL REFERENCES packages(id) ON DELETE CASCADE,
|
||||
table_name TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (package_id, table_name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_data_tables_pkg ON ext_data_tables(package_id);
|
||||
|
||||
-- ── Platform Read View ──────────────────────
|
||||
|
||||
CREATE OR REPLACE VIEW ext_view_users AS
|
||||
SELECT id, display_name, email FROM users;
|
||||
66
server/database/migrations/postgres/004_connections.sql
Normal file
66
server/database/migrations/postgres/004_connections.sql
Normal file
@@ -0,0 +1,66 @@
|
||||
-- ==========================================
|
||||
-- Switchboard Core — 004 Connections & Dependencies
|
||||
-- ==========================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ext_connections (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type TEXT NOT NULL,
|
||||
package_id TEXT NOT NULL,
|
||||
scope VARCHAR(10) NOT NULL
|
||||
CHECK (scope IN ('global', 'team', 'personal')),
|
||||
owner_id TEXT NOT NULL DEFAULT '',
|
||||
name TEXT NOT NULL,
|
||||
config JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(type, scope, owner_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_connections_type ON ext_connections(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_connections_scope ON ext_connections(scope);
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_connections_owner ON ext_connections(owner_id) WHERE owner_id != '';
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_connections_active ON ext_connections(is_active) WHERE is_active = true;
|
||||
|
||||
DROP TRIGGER IF EXISTS ext_connections_updated_at ON ext_connections;
|
||||
CREATE TRIGGER ext_connections_updated_at BEFORE UPDATE ON ext_connections
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
|
||||
-- ── Extension Dependencies ──────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ext_dependencies (
|
||||
consumer_id TEXT NOT NULL,
|
||||
library_id TEXT NOT NULL,
|
||||
version_spec TEXT NOT NULL DEFAULT '>=0.0.0',
|
||||
resolved_ver TEXT NOT NULL DEFAULT '0.0.0',
|
||||
PRIMARY KEY (consumer_id, library_id),
|
||||
FOREIGN KEY (consumer_id) REFERENCES packages(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (library_id) REFERENCES packages(id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_deps_consumer ON ext_dependencies(consumer_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ext_deps_library ON ext_dependencies(library_id);
|
||||
|
||||
-- ── Resource Grants ─────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS resource_grants (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
resource_type VARCHAR(30) NOT NULL,
|
||||
resource_id UUID NOT NULL,
|
||||
grant_scope VARCHAR(20) NOT NULL DEFAULT 'team_only'
|
||||
CHECK (grant_scope IN ('team_only', 'global', 'groups')),
|
||||
granted_groups UUID[] NOT NULL DEFAULT '{}',
|
||||
created_by UUID NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(resource_type, resource_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_grants_resource
|
||||
ON resource_grants(resource_type, resource_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_grants_groups
|
||||
ON resource_grants USING gin(granted_groups);
|
||||
|
||||
DROP TRIGGER IF EXISTS resource_grants_updated_at ON resource_grants;
|
||||
CREATE TRIGGER resource_grants_updated_at BEFORE UPDATE ON resource_grants
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
31
server/database/migrations/postgres/005_notifications.sql
Normal file
31
server/database/migrations/postgres/005_notifications.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
-- ==========================================
|
||||
-- Switchboard Core — 005 Notifications
|
||||
-- ==========================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
body TEXT DEFAULT '',
|
||||
resource_type VARCHAR(50),
|
||||
resource_id UUID,
|
||||
is_read BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notifications_user_unread
|
||||
ON notifications(user_id, is_read, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_notifications_user_created
|
||||
ON notifications(user_id, created_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notification_preferences (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
in_app BOOLEAN NOT NULL DEFAULT true,
|
||||
email BOOLEAN NOT NULL DEFAULT false,
|
||||
UNIQUE(user_id, type)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notification_prefs_user ON notification_preferences(user_id);
|
||||
20
server/database/migrations/postgres/006_audit.sql
Normal file
20
server/database/migrations/postgres/006_audit.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
-- ==========================================
|
||||
-- Switchboard Core — 006 Audit
|
||||
-- ==========================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
actor_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
action VARCHAR(100) NOT NULL,
|
||||
resource_type VARCHAR(50) NOT NULL,
|
||||
resource_id VARCHAR(255),
|
||||
metadata JSONB DEFAULT '{}'::jsonb,
|
||||
ip_address VARCHAR(45),
|
||||
user_agent TEXT DEFAULT '',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_actor ON audit_log(actor_id) WHERE actor_id IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_resource ON audit_log(resource_type, resource_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_action ON audit_log(action);
|
||||
76
server/database/migrations/postgres/007_workflows.sql
Normal file
76
server/database/migrations/postgres/007_workflows.sql
Normal file
@@ -0,0 +1,76 @@
|
||||
-- ==========================================
|
||||
-- Switchboard Core — 007 Workflows
|
||||
-- ==========================================
|
||||
-- Workflow definitions, stages, version snapshots.
|
||||
-- persona_id kept as nullable TEXT (no FK — personas are extensions now).
|
||||
-- workflow_assignments dropped (channel-dependent, rebuild as needed).
|
||||
-- ==========================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS workflows (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
team_id UUID REFERENCES teams(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
branding JSONB NOT NULL DEFAULT '{}',
|
||||
entry_mode TEXT NOT NULL DEFAULT 'public_link'
|
||||
CHECK (entry_mode IN ('public_link', 'team_only')),
|
||||
is_active BOOLEAN NOT NULL DEFAULT false,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
on_complete JSONB,
|
||||
retention JSONB NOT NULL DEFAULT '{"mode": "archive"}',
|
||||
webhook_url TEXT,
|
||||
webhook_secret TEXT,
|
||||
created_by UUID NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_workflows_team_slug
|
||||
ON workflows(team_id, slug) WHERE team_id IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_workflows_global_slug
|
||||
ON workflows(slug) WHERE team_id IS NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_workflows_active
|
||||
ON workflows(is_active) WHERE is_active = true;
|
||||
|
||||
DROP TRIGGER IF EXISTS workflows_updated_at ON workflows;
|
||||
CREATE TRIGGER workflows_updated_at BEFORE UPDATE ON workflows
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
|
||||
-- ── Workflow Stages ─────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS workflow_stages (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workflow_id UUID NOT NULL REFERENCES workflows(id) ON DELETE CASCADE,
|
||||
ordinal INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
persona_id TEXT,
|
||||
assignment_team_id UUID REFERENCES teams(id) ON DELETE SET NULL,
|
||||
form_template JSONB NOT NULL DEFAULT '{}',
|
||||
stage_mode TEXT NOT NULL DEFAULT 'form_only'
|
||||
CHECK (stage_mode IN ('form_only', 'form_chat', 'review', 'custom')),
|
||||
history_mode TEXT NOT NULL DEFAULT 'full'
|
||||
CHECK (history_mode IN ('full', 'summary', 'fresh')),
|
||||
auto_transition BOOLEAN NOT NULL DEFAULT false,
|
||||
transition_rules JSONB NOT NULL DEFAULT '{}',
|
||||
surface_pkg_id TEXT REFERENCES packages(id) ON DELETE SET NULL,
|
||||
sla_seconds INTEGER,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_stages_workflow
|
||||
ON workflow_stages(workflow_id, ordinal);
|
||||
|
||||
-- ── Workflow Versions ───────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS workflow_versions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
workflow_id UUID NOT NULL REFERENCES workflows(id) ON DELETE CASCADE,
|
||||
version_number INTEGER NOT NULL,
|
||||
snapshot JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (workflow_id, version_number)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_versions_workflow
|
||||
ON workflow_versions(workflow_id, version_number DESC);
|
||||
69
server/database/migrations/postgres/008_tasks.sql
Normal file
69
server/database/migrations/postgres/008_tasks.sql
Normal file
@@ -0,0 +1,69 @@
|
||||
-- ==========================================
|
||||
-- Switchboard Core — 008 Tasks
|
||||
-- ==========================================
|
||||
-- Task scheduling and run history.
|
||||
-- Stripped: persona_id (kept nullable, no FK), output_channel_id,
|
||||
-- provider_config_id. Output modes: notification | webhook | log.
|
||||
-- ==========================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
team_id UUID REFERENCES teams(id) ON DELETE SET NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
scope TEXT NOT NULL DEFAULT 'personal'
|
||||
CHECK (scope IN ('personal', 'team', 'global')),
|
||||
task_type TEXT NOT NULL DEFAULT 'action'
|
||||
CHECK (task_type IN ('action', 'workflow', 'system')),
|
||||
system_function TEXT DEFAULT '',
|
||||
persona_id TEXT,
|
||||
model_id TEXT,
|
||||
system_prompt TEXT DEFAULT '',
|
||||
user_prompt TEXT DEFAULT '',
|
||||
workflow_id UUID REFERENCES workflows(id) ON DELETE SET NULL,
|
||||
tool_grants JSONB,
|
||||
schedule TEXT NOT NULL,
|
||||
timezone TEXT NOT NULL DEFAULT 'UTC',
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
trigger_token TEXT UNIQUE,
|
||||
max_tokens INTEGER NOT NULL DEFAULT 4096,
|
||||
max_tool_calls INTEGER NOT NULL DEFAULT 10,
|
||||
max_wall_clock INTEGER NOT NULL DEFAULT 300,
|
||||
output_mode TEXT NOT NULL DEFAULT 'log'
|
||||
CHECK (output_mode IN ('notification', 'webhook', 'log')),
|
||||
webhook_url TEXT,
|
||||
webhook_secret TEXT,
|
||||
notify_on_complete BOOLEAN NOT NULL DEFAULT false,
|
||||
notify_on_failure BOOLEAN NOT NULL DEFAULT true,
|
||||
last_run_at TIMESTAMPTZ,
|
||||
next_run_at TIMESTAMPTZ,
|
||||
run_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_next_run ON tasks(next_run_at) WHERE is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_owner ON tasks(owner_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tasks_team ON tasks(team_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_trigger_token
|
||||
ON tasks(trigger_token) WHERE trigger_token IS NOT NULL;
|
||||
|
||||
-- ── Task Runs ───────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS task_runs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
status TEXT NOT NULL DEFAULT 'running'
|
||||
CHECK (status IN ('queued', 'running', 'completed', 'failed', 'budget_exceeded', 'cancelled')),
|
||||
trigger_payload TEXT,
|
||||
started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
completed_at TIMESTAMPTZ,
|
||||
tokens_used INTEGER DEFAULT 0,
|
||||
tool_calls INTEGER DEFAULT 0,
|
||||
wall_clock INTEGER DEFAULT 0,
|
||||
error TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_task_runs_task ON task_runs(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_task_runs_status ON task_runs(task_id, status);
|
||||
21
server/database/migrations/postgres/009_ha.sql
Normal file
21
server/database/migrations/postgres/009_ha.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
-- ==========================================
|
||||
-- Switchboard Core — 009 Multi-Replica HA
|
||||
-- ==========================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ws_tickets (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
expires_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ws_tickets_expires ON ws_tickets(expires_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rate_limit_counters (
|
||||
key TEXT NOT NULL,
|
||||
window_start TIMESTAMPTZ NOT NULL,
|
||||
tokens REAL NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (key, window_start)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rate_limit_counters_window
|
||||
ON rate_limit_counters(window_start);
|
||||
Reference in New Issue
Block a user