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:
2026-03-25 20:22:02 -04:00
parent c3e9dcf731
commit ebea16344c
54 changed files with 487 additions and 2815 deletions

View 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);

View 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;

View 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;

View 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();

View 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);

View 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);

View 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);

View 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);

View 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);