Changeset 0.26.0 (#165)
This commit is contained in:
86
server/database/migrations/023_workflows.sql
Normal file
86
server/database/migrations/023_workflows.sql
Normal file
@@ -0,0 +1,86 @@
|
||||
-- 023_workflows.sql
|
||||
-- Workflow definitions, stages, and version snapshots (v0.26.1)
|
||||
-- Depends on: teams, personas, users
|
||||
|
||||
-- ── Workflow Definitions ────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS workflows (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
team_id UUID REFERENCES teams(id) ON DELETE CASCADE, -- NULL = global
|
||||
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, -- v0.27.0 chaining hook, NULL for now
|
||||
retention JSONB NOT NULL DEFAULT '{"mode": "archive"}',
|
||||
created_by UUID NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Slug unique within scope: team-scoped workflows + global workflows
|
||||
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;
|
||||
|
||||
COMMENT ON TABLE workflows IS 'Team-owned or global workflow definitions with staged processes.';
|
||||
COMMENT ON COLUMN workflows.slug IS 'URL-safe identifier, unique within scope (team or global).';
|
||||
COMMENT ON COLUMN workflows.branding IS '{"accent_color": "#hex", "logo_url": "...", "tagline": "..."}';
|
||||
COMMENT ON COLUMN workflows.on_complete IS 'v0.27.0: chaining hook — triggers another workflow on completion.';
|
||||
COMMENT ON COLUMN workflows.retention IS '{"mode": "archive"|"delete", "delete_after_days": N}';
|
||||
|
||||
-- ── 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 UUID REFERENCES personas(id) ON DELETE SET NULL,
|
||||
assignment_team_id UUID REFERENCES teams(id) ON DELETE SET NULL,
|
||||
form_template JSONB NOT NULL DEFAULT '{}',
|
||||
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 '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_stages_workflow
|
||||
ON workflow_stages(workflow_id, ordinal);
|
||||
|
||||
COMMENT ON TABLE workflow_stages IS 'Ordered stages within a workflow. Each stage has a driving persona and optional human assignment.';
|
||||
COMMENT ON COLUMN workflow_stages.history_mode IS 'full=complete history, summary=utility-role summary, fresh=clean slate';
|
||||
COMMENT ON COLUMN workflow_stages.form_template IS 'JSON schema of fields the persona should collect from the visitor.';
|
||||
|
||||
-- ── Workflow Versions (immutable snapshots) ─
|
||||
|
||||
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);
|
||||
|
||||
COMMENT ON TABLE workflow_versions IS 'Immutable snapshots of workflow definitions. Active instances run against a pinned version.';
|
||||
COMMENT ON COLUMN workflow_versions.snapshot IS 'Full JSON: definition + stages + persona tool grants at publish time.';
|
||||
|
||||
-- ── updated_at trigger ──────────────────────
|
||||
|
||||
CREATE TRIGGER workflows_updated_at
|
||||
BEFORE UPDATE ON workflows
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
||||
25
server/database/migrations/024_workflow_instances.sql
Normal file
25
server/database/migrations/024_workflow_instances.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- 024_workflow_instances.sql
|
||||
-- Workflow instance columns on channels (v0.26.2)
|
||||
-- Channels with type='workflow' become runtime containers for workflow instances.
|
||||
|
||||
ALTER TABLE channels
|
||||
ADD COLUMN IF NOT EXISTS workflow_id UUID REFERENCES workflows(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS workflow_version INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS current_stage INTEGER DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS stage_data JSONB DEFAULT '{}',
|
||||
ADD COLUMN IF NOT EXISTS workflow_status TEXT DEFAULT 'active'
|
||||
CHECK (workflow_status IN ('active', 'completed', 'stale', 'cancelled')),
|
||||
ADD COLUMN IF NOT EXISTS last_activity_at TIMESTAMPTZ DEFAULT NOW();
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channels_workflow
|
||||
ON channels(workflow_id) WHERE workflow_id IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channels_workflow_status
|
||||
ON channels(workflow_status) WHERE workflow_status = 'active';
|
||||
|
||||
COMMENT ON COLUMN channels.workflow_id IS 'FK to workflows — set when type=workflow';
|
||||
COMMENT ON COLUMN channels.workflow_version IS 'Pinned version number from workflow_versions';
|
||||
COMMENT ON COLUMN channels.current_stage IS 'Ordinal of the active workflow stage';
|
||||
COMMENT ON COLUMN channels.stage_data IS 'Accumulated form data collected across stages';
|
||||
COMMENT ON COLUMN channels.workflow_status IS 'active/completed/stale/cancelled';
|
||||
COMMENT ON COLUMN channels.last_activity_at IS 'Updated on each message — used by staleness sweep';
|
||||
27
server/database/migrations/025_workflow_assignments.sql
Normal file
27
server/database/migrations/025_workflow_assignments.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
-- 025_workflow_assignments.sql
|
||||
-- Assignment queue for workflow stages (v0.26.4)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS workflow_assignments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
channel_id UUID NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
stage INTEGER NOT NULL,
|
||||
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
||||
assigned_to UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
status TEXT NOT NULL DEFAULT 'unassigned'
|
||||
CHECK (status IN ('unassigned', 'claimed', 'completed')),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
claimed_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_assignments_channel
|
||||
ON workflow_assignments(channel_id, stage);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_assignments_team_status
|
||||
ON workflow_assignments(team_id, status) WHERE status = 'unassigned';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_assignments_user
|
||||
ON workflow_assignments(assigned_to) WHERE assigned_to IS NOT NULL;
|
||||
|
||||
COMMENT ON TABLE workflow_assignments IS 'Queue entries for human review stages in workflows.';
|
||||
COMMENT ON COLUMN workflow_assignments.status IS 'unassigned=waiting, claimed=being worked, completed=done';
|
||||
56
server/database/migrations/sqlite/023_workflows.sql
Normal file
56
server/database/migrations/sqlite/023_workflows.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
-- 023_workflows.sql (SQLite)
|
||||
-- Workflow definitions, stages, and version snapshots (v0.26.1)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS workflows (
|
||||
id TEXT PRIMARY KEY,
|
||||
team_id TEXT REFERENCES teams(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
branding TEXT NOT NULL DEFAULT '{}',
|
||||
entry_mode TEXT NOT NULL DEFAULT 'public_link'
|
||||
CHECK (entry_mode IN ('public_link', 'team_only')),
|
||||
is_active INTEGER NOT NULL DEFAULT 0,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
on_complete TEXT,
|
||||
retention TEXT NOT NULL DEFAULT '{"mode": "archive"}',
|
||||
created_by TEXT NOT NULL REFERENCES users(id),
|
||||
created_at DATETIME NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at DATETIME NOT NULL DEFAULT (datetime('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 TABLE IF NOT EXISTS workflow_stages (
|
||||
id TEXT PRIMARY KEY,
|
||||
workflow_id TEXT NOT NULL REFERENCES workflows(id) ON DELETE CASCADE,
|
||||
ordinal INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
persona_id TEXT REFERENCES personas(id) ON DELETE SET NULL,
|
||||
assignment_team_id TEXT REFERENCES teams(id) ON DELETE SET NULL,
|
||||
form_template TEXT NOT NULL DEFAULT '{}',
|
||||
history_mode TEXT NOT NULL DEFAULT 'full'
|
||||
CHECK (history_mode IN ('full', 'summary', 'fresh')),
|
||||
auto_transition INTEGER NOT NULL DEFAULT 0,
|
||||
transition_rules TEXT NOT NULL DEFAULT '{}',
|
||||
created_at DATETIME NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_stages_workflow
|
||||
ON workflow_stages(workflow_id, ordinal);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS workflow_versions (
|
||||
id TEXT PRIMARY KEY,
|
||||
workflow_id TEXT NOT NULL REFERENCES workflows(id) ON DELETE CASCADE,
|
||||
version_number INTEGER NOT NULL,
|
||||
snapshot TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT (datetime('now')),
|
||||
|
||||
UNIQUE (workflow_id, version_number)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_versions_workflow
|
||||
ON workflow_versions(workflow_id, version_number);
|
||||
13
server/database/migrations/sqlite/024_workflow_instances.sql
Normal file
13
server/database/migrations/sqlite/024_workflow_instances.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- 024_workflow_instances.sql (SQLite)
|
||||
-- Workflow instance columns on channels (v0.26.2)
|
||||
|
||||
ALTER TABLE channels ADD COLUMN workflow_id TEXT REFERENCES workflows(id) ON DELETE SET NULL;
|
||||
ALTER TABLE channels ADD COLUMN workflow_version INTEGER;
|
||||
ALTER TABLE channels ADD COLUMN current_stage INTEGER DEFAULT 0;
|
||||
ALTER TABLE channels ADD COLUMN stage_data TEXT DEFAULT '{}';
|
||||
ALTER TABLE channels ADD COLUMN workflow_status TEXT DEFAULT 'active'
|
||||
CHECK (workflow_status IN ('active', 'completed', 'stale', 'cancelled'));
|
||||
ALTER TABLE channels ADD COLUMN last_activity_at DATETIME DEFAULT (datetime('now'));
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channels_workflow
|
||||
ON channels(workflow_id) WHERE workflow_id IS NOT NULL;
|
||||
@@ -0,0 +1,21 @@
|
||||
-- 025_workflow_assignments.sql (SQLite)
|
||||
-- Assignment queue for workflow stages (v0.26.4)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS workflow_assignments (
|
||||
id TEXT PRIMARY KEY,
|
||||
channel_id TEXT NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
|
||||
stage INTEGER NOT NULL,
|
||||
team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
||||
assigned_to TEXT REFERENCES users(id) ON DELETE SET NULL,
|
||||
status TEXT NOT NULL DEFAULT 'unassigned'
|
||||
CHECK (status IN ('unassigned', 'claimed', 'completed')),
|
||||
created_at DATETIME NOT NULL DEFAULT (datetime('now')),
|
||||
claimed_at DATETIME,
|
||||
completed_at DATETIME
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_assignments_channel
|
||||
ON workflow_assignments(channel_id, stage);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_workflow_assignments_team_status
|
||||
ON workflow_assignments(team_id, status);
|
||||
Reference in New Issue
Block a user