Brings the deterministic Han-flow conductor into BooCode: launch any read-only flow from BooChat or BooCoder, watch each agent stream live in a Paseo-style run pane, get an evidence-disciplined report — on local Qwen, persisted and resumable. Read-only enforced hard via qwen --approval-mode plan (orchestrator tasks fail closed if qwen is unavailable; never fall to write-capable native). Backend (apps/coder): re-homed conductor defs, flow_runs/flow_steps schema, flow-runner + dispatcher onTaskTerminal hook, restart-resume, runs routes (launch/list/get/cancel), user-channel WS. Contracts: two flow_run_* frames. Web: orchestrator pane kind + OrchestratorPane, Workflow button + slash flows (BooChat/BooCoder parity), FlowLauncherDialog, "New Orchestrator" in the + and split menus, runs history + export. Plan: openspec/changes/orchestrator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
360 lines
18 KiB
PL/PgSQL
360 lines
18 KiB
PL/PgSQL
-- v2.0.0: BooCoder schema — pending changes, tasks, agent registry.
|
|
-- Applied on startup by apps/coder/src/db.ts:applySchema().
|
|
-- Lives in the same 'boochat' database as BooChat's tables.
|
|
|
|
CREATE TABLE IF NOT EXISTS pending_changes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
session_id UUID NOT NULL,
|
|
task_id UUID,
|
|
file_path TEXT NOT NULL,
|
|
operation TEXT NOT NULL,
|
|
diff TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
CONSTRAINT pending_changes_operation_chk CHECK (operation IN ('create', 'edit', 'delete')),
|
|
CONSTRAINT pending_changes_status_chk CHECK (status IN ('pending', 'applied', 'rejected', 'reverted'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
project_id UUID NOT NULL,
|
|
parent_task_id UUID REFERENCES tasks(id),
|
|
state TEXT NOT NULL DEFAULT 'pending',
|
|
input TEXT NOT NULL,
|
|
output_summary TEXT,
|
|
agent TEXT,
|
|
model TEXT,
|
|
execution_path TEXT,
|
|
cost_tokens INTEGER,
|
|
started_at TIMESTAMPTZ,
|
|
ended_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
CONSTRAINT tasks_state_chk CHECK (state IN ('pending', 'running', 'completed', 'failed', 'blocked', 'cancelled')),
|
|
CONSTRAINT tasks_execution_path_chk CHECK (execution_path IS NULL OR execution_path IN ('native', 'acp', 'pty', 'qwen'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS available_agents (
|
|
name TEXT PRIMARY KEY,
|
|
install_path TEXT,
|
|
version TEXT,
|
|
supports_acp BOOLEAN NOT NULL DEFAULT false,
|
|
last_probed_at TIMESTAMPTZ
|
|
);
|
|
ALTER TABLE available_agents DROP COLUMN IF EXISTS supports_mcp_client;
|
|
|
|
-- v2.0.0 Phase 4: link tasks to their inference sessions.
|
|
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS session_id UUID REFERENCES sessions(id);
|
|
|
|
-- v2.0.5: add 'qwen' to execution_path CHECK + arena_id column.
|
|
ALTER TABLE tasks DROP CONSTRAINT IF EXISTS tasks_execution_path_chk;
|
|
DO $$ BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'tasks_execution_path_chk') THEN
|
|
ALTER TABLE tasks ADD CONSTRAINT tasks_execution_path_chk
|
|
CHECK (execution_path IS NULL OR execution_path IN ('native', 'acp', 'pty', 'qwen'));
|
|
END IF;
|
|
END $$;
|
|
|
|
-- v2.0.5: arena support — group tasks into competitive arenas.
|
|
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS arena_id UUID;
|
|
|
|
-- Human inbox: tasks needing attention
|
|
CREATE OR REPLACE VIEW human_inbox AS
|
|
SELECT * FROM tasks WHERE state IN ('blocked', 'failed');
|
|
|
|
-- v2.1.0: provider picker — extend available_agents with model discovery.
|
|
ALTER TABLE available_agents ADD COLUMN IF NOT EXISTS models JSONB DEFAULT '[]'::jsonb;
|
|
ALTER TABLE available_agents ADD COLUMN IF NOT EXISTS label TEXT;
|
|
ALTER TABLE available_agents ADD COLUMN IF NOT EXISTS transport TEXT DEFAULT 'pty';
|
|
-- v2.5.10: persisted ACP available_commands (captured during the cold probe), so
|
|
-- an agent's live command set survives the tier-2 probe skip and shows without a
|
|
-- dispatch.
|
|
ALTER TABLE available_agents ADD COLUMN IF NOT EXISTS commands JSONB DEFAULT '[]'::jsonb;
|
|
|
|
-- v2.2.0: Paseo-style session config on tasks.
|
|
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS mode_id TEXT;
|
|
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS thinking_option_id TEXT;
|
|
-- tasks.feature_values and tasks.worktree_path were never read or written by any
|
|
-- code path; drop them from existing DBs (fresh DBs never had them in the CREATE).
|
|
-- human_inbox is `SELECT *` over tasks, so it pins every task column — dropping a
|
|
-- column while the view exists fails (2BP01). Drop the view, drop the columns, then
|
|
-- recreate it with the current column set (idempotent on fresh + existing DBs).
|
|
DROP VIEW IF EXISTS human_inbox;
|
|
ALTER TABLE tasks DROP COLUMN IF EXISTS feature_values;
|
|
ALTER TABLE tasks DROP COLUMN IF EXISTS worktree_path;
|
|
CREATE OR REPLACE VIEW human_inbox AS
|
|
SELECT * FROM tasks WHERE state IN ('blocked', 'failed');
|
|
|
|
-- v2.6: one backend session per (session, agent); resumed on switch-back.
|
|
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
session_id UUID NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
agent TEXT NOT NULL,
|
|
backend TEXT NOT NULL,
|
|
agent_session_id TEXT,
|
|
server_port INTEGER,
|
|
status TEXT NOT NULL DEFAULT 'idle',
|
|
last_active_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
PRIMARY KEY (session_id, agent),
|
|
CONSTRAINT agent_sessions_backend_chk CHECK (backend IN ('opencode_server', 'acp_warm')),
|
|
CONSTRAINT agent_sessions_status_chk CHECK (status IN ('idle', 'active', 'crashed', 'closed'))
|
|
);
|
|
|
|
-- Migrate existing agent_sessions FK to CASCADE.
|
|
DO $$ BEGIN
|
|
IF EXISTS (
|
|
SELECT 1 FROM pg_constraint
|
|
WHERE conname = 'agent_sessions_session_id_fkey'
|
|
AND confdeltype <> 'c'
|
|
) THEN
|
|
ALTER TABLE agent_sessions DROP CONSTRAINT agent_sessions_session_id_fkey;
|
|
ALTER TABLE agent_sessions ADD CONSTRAINT agent_sessions_session_id_fkey
|
|
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- v2.6: config fingerprint for stale-session detection (auto-recover on model change).
|
|
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS config_hash TEXT;
|
|
|
|
-- v2.6 Phase 1-UX (U.6): opencode token/cost usage, ACCUMULATED per (chat_id, agent).
|
|
-- opencode's warm server emits `session.next.step.ended` once per LLM step (several
|
|
-- per multi-tool turn) carrying {tokens{input,output,reasoning,cache},cost}. We sum
|
|
-- each step's normalized {input,output,cost} onto the session row — running totals
|
|
-- for the whole conversation context, not last-step. Backend-only; no route/UI yet.
|
|
-- input_tokens folds in cache read+write; output_tokens folds in reasoning (see
|
|
-- backends/opencode-usage.ts). Defaults 0 so accumulation (col + delta) is well-defined.
|
|
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS input_tokens BIGINT NOT NULL DEFAULT 0;
|
|
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS output_tokens BIGINT NOT NULL DEFAULT 0;
|
|
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS cost DOUBLE PRECISION NOT NULL DEFAULT 0;
|
|
|
|
-- ─── P1.5-b (corrected): worktrees entity + re-key agent_sessions to (chat_id, agent) ───
|
|
-- The TAB (a chat) is the context unit: two opencode tabs in one session = two
|
|
-- independent contexts sharing one worktree. So agent_sessions keys on
|
|
-- (chat_id, agent), NOT (worktree_id, agent) or (session_id, agent). The
|
|
-- `worktrees` table is one-per-session (selectable later) and only referenced
|
|
-- informationally by agent_sessions.worktree_id (SET NULL); chat_id is the key.
|
|
--
|
|
-- PREREQUISITE: the unmigratable test session (35 chats, 1 agent_sessions row that
|
|
-- maps to no single chat) is DELETED before this runs, so agent_sessions is empty
|
|
-- and the chat_id backfill is N/A. If a row with NULL chat_id remains, the verify
|
|
-- gate below RAISEs and aborts — delete the offending session first.
|
|
|
|
-- worktree as a first-class entity; survives session delete (session_id SET NULL).
|
|
CREATE TABLE IF NOT EXISTS worktrees (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
session_id UUID REFERENCES sessions(id) ON DELETE SET NULL,
|
|
project_id UUID,
|
|
path TEXT NOT NULL,
|
|
branch TEXT,
|
|
base_commit TEXT,
|
|
slug TEXT,
|
|
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','archived')),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS worktrees_active_path_uidx ON worktrees(path) WHERE status='active';
|
|
|
|
-- session_worktrees was superseded by worktrees (v2.6/P1.5-b); all rows migrated
|
|
-- before P2 cleanup. Drop the dead table; no-op on fresh DBs that never had it.
|
|
DROP TABLE IF EXISTS session_worktrees;
|
|
|
|
-- Dispatch hint: which chat (tab) a task belongs to. The coder message route and
|
|
-- skills route set it from the frontend tab; session-less creators (arena, MCP,
|
|
-- new_task, generic /api/tasks) leave it NULL and the dispatcher creates a chat.
|
|
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS chat_id UUID REFERENCES chats(id) ON DELETE SET NULL;
|
|
|
|
-- Re-key columns on agent_sessions.
|
|
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS chat_id UUID;
|
|
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS worktree_id UUID;
|
|
|
|
-- BACKFILL-VERIFY GATE: the new PK is (chat_id, agent), so chat_id must be
|
|
-- non-null on every row before the swap. With the test session deleted this is a
|
|
-- 0-row assertion; if any row has NULL chat_id (an unmigratable pre-existing row),
|
|
-- abort loudly rather than create a degenerate (NULL, agent) key.
|
|
DO $$
|
|
DECLARE n int;
|
|
BEGIN
|
|
SELECT count(*) INTO n FROM agent_sessions WHERE chat_id IS NULL;
|
|
IF n > 0 THEN
|
|
RAISE EXCEPTION 'P1.5-b: % agent_sessions row(s) have NULL chat_id — delete the unmigratable session(s) before applying', n;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Swap PK (session_id,agent) → (chat_id,agent) + FKs (run-once, guarded on the new
|
|
-- FK's absence). chat_id CASCADEs from chats (closing a tab ends its context);
|
|
-- worktree_id is informational SET NULL; session_id defanged to nullable SET NULL.
|
|
DO $$ BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'agent_sessions_chat_id_fkey') THEN
|
|
ALTER TABLE agent_sessions DROP CONSTRAINT IF EXISTS agent_sessions_pkey;
|
|
ALTER TABLE agent_sessions DROP CONSTRAINT IF EXISTS agent_sessions_session_id_fkey;
|
|
ALTER TABLE agent_sessions ALTER COLUMN session_id DROP NOT NULL;
|
|
ALTER TABLE agent_sessions ALTER COLUMN chat_id SET NOT NULL;
|
|
ALTER TABLE agent_sessions ADD CONSTRAINT agent_sessions_pkey PRIMARY KEY (chat_id, agent);
|
|
ALTER TABLE agent_sessions ADD CONSTRAINT agent_sessions_chat_id_fkey
|
|
FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
|
ALTER TABLE agent_sessions ADD CONSTRAINT agent_sessions_session_id_fkey
|
|
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE SET NULL;
|
|
ALTER TABLE agent_sessions ADD CONSTRAINT agent_sessions_worktree_id_fkey
|
|
FOREIGN KEY (worktree_id) REFERENCES worktrees(id) ON DELETE SET NULL;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- P1.5-b follow-up: converge agent_sessions.session_id FK CASCADE → SET NULL.
|
|
-- The re-key block above re-adds session_id_fkey as SET NULL, but it is guarded on
|
|
-- chat_id_fkey's ABSENCE — so a DB already re-keyed to (chat_id, agent) while
|
|
-- session_id_fkey was still ON DELETE CASCADE never re-enters that block and stays
|
|
-- 'c'. This standalone guard flips it to SET NULL ('n'), matching worktree_id.
|
|
-- Idempotent (mirrors the session_worktrees defang's confdeltype check): only fires
|
|
-- while the FK is still CASCADE — a no-op on a fresh deploy (already 'n' from the
|
|
-- re-key block) and on every re-run thereafter.
|
|
DO $$ BEGIN
|
|
IF EXISTS (
|
|
SELECT 1 FROM pg_constraint
|
|
WHERE conname = 'agent_sessions_session_id_fkey'
|
|
AND confdeltype = 'c'
|
|
) THEN
|
|
ALTER TABLE agent_sessions ALTER COLUMN session_id DROP NOT NULL;
|
|
ALTER TABLE agent_sessions DROP CONSTRAINT agent_sessions_session_id_fkey;
|
|
ALTER TABLE agent_sessions ADD CONSTRAINT agent_sessions_session_id_fkey
|
|
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE SET NULL;
|
|
END IF;
|
|
END $$;
|
|
|
|
-- v2.6: attribution for DiffPanel badges (Phase 1 UX reads this).
|
|
ALTER TABLE pending_changes ADD COLUMN IF NOT EXISTS agent TEXT;
|
|
|
|
-- write-edit-robustness #4: worktree checkpoints. A pre-turn shadow-commit of the
|
|
-- session worktree (tracked + untracked, captured without disturbing the real
|
|
-- index/working tree) stored in a private GC-safe ref refs/boocode/checkpoints/<id>.
|
|
-- Created best-effort before each external-agent turn (opencode / warm-ACP / one-shot
|
|
-- ACP+PTY); restore resets the worktree to commit_sha, trims the transcript from
|
|
-- message_id forward, and resets the backend session. chat_id CASCADEs from chats
|
|
-- (like agent_sessions); worktree_id SET NULL so a checkpoint outlives a reaped
|
|
-- worktree row. session_id / message_id are informational (no FK — message rows are
|
|
-- trimmed by a checkpoint restore and we must not block that on a dangling ref).
|
|
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
chat_id UUID NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
|
|
session_id UUID,
|
|
worktree_id UUID REFERENCES worktrees(id) ON DELETE SET NULL,
|
|
message_id UUID, -- anchor: the assistant turn row this checkpoint precedes
|
|
commit_sha TEXT NOT NULL, -- shadow-commit capturing the pre-turn worktree tree
|
|
label TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS checkpoints_chat_created_idx ON checkpoints(chat_id, created_at);
|
|
|
|
-- claude-sdk-sessionstore #9 (Part 1): append-only mirror of Claude Agent SDK
|
|
-- session transcripts. The SDK's SessionStore adapter writes one JSONL line per
|
|
-- entry; PostgresSessionStore (services/backends/claude-session-store.ts) inserts
|
|
-- one row per entry and replays them ORDER BY id on resume. The store is generic
|
|
-- per the SDK's SessionKey (project_key, session_id, subpath) — chat↔session
|
|
-- ownership lives in agent_sessions, not here. subpath '' is the main transcript
|
|
-- (the SDK's undefined subpath maps to '' in the column).
|
|
CREATE TABLE IF NOT EXISTS claude_session_entries (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
project_key TEXT NOT NULL,
|
|
session_id TEXT NOT NULL,
|
|
subpath TEXT NOT NULL DEFAULT '', -- '' = main transcript (SDK's undefined subpath maps here)
|
|
entry JSONB NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS claude_session_entries_key_idx ON claude_session_entries (project_key, session_id, subpath, id);
|
|
|
|
-- claude-sdk-sessionstore #9 (Part 2): the warm Claude-SDK backend persists its
|
|
-- agent_sessions rows with backend='claude_sdk'. Widen the named CHECK to accept
|
|
-- it. Idempotent: DROP the named constraint (the inline CREATE TABLE check above
|
|
-- carries this explicit name, so DROP IF EXISTS targets it) + re-ADD the widened
|
|
-- list. Re-runs/fresh deploys land on the same final constraint (the table-level
|
|
-- CREATE already includes only the old two values on a fresh DB; this block then
|
|
-- replaces it with the three-value list).
|
|
ALTER TABLE agent_sessions DROP CONSTRAINT IF EXISTS agent_sessions_backend_chk;
|
|
ALTER TABLE agent_sessions ADD CONSTRAINT agent_sessions_backend_chk
|
|
CHECK (backend IN ('opencode_server', 'acp_warm', 'claude_sdk'));
|
|
|
|
-- LISTEN/NOTIFY fast path: every tasks INSERT (from any call site — routes,
|
|
-- new_task tool, arena, MCP server) fires pg_notify('tasks_new') in the same
|
|
-- transaction, so the dispatcher reacts immediately instead of waiting for the
|
|
-- fallback poll. Postgres holds the notification until COMMIT, so the listener
|
|
-- always sees the committed row. A trigger covers all insert paths with no
|
|
-- app-code drift. Idempotent: re-applied on every startup.
|
|
CREATE OR REPLACE FUNCTION notify_tasks_new() RETURNS trigger AS $$
|
|
BEGIN
|
|
PERFORM pg_notify('tasks_new', '');
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS tasks_notify_new ON tasks;
|
|
CREATE TRIGGER tasks_notify_new
|
|
AFTER INSERT ON tasks
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION notify_tasks_new();
|
|
|
|
-- v2.8.0: orchestrator flow_runs + flow_steps — DB-backed scheduler for multi-agent flows.
|
|
-- project_id carries no FK (matches tasks.project_id convention, schema.sql:19).
|
|
CREATE TABLE IF NOT EXISTS flow_runs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
project_id UUID NOT NULL,
|
|
flow_name TEXT NOT NULL,
|
|
band TEXT NOT NULL,
|
|
model TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'running',
|
|
input JSONB NOT NULL,
|
|
report TEXT,
|
|
error TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
CONSTRAINT flow_runs_band_chk CHECK (band IN ('small', 'medium', 'large')),
|
|
CONSTRAINT flow_runs_status_chk CHECK (status IN ('running', 'completed', 'failed', 'cancelled')),
|
|
CONSTRAINT flow_runs_input_chk CHECK (input ? 'question')
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS flow_steps (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
run_id UUID NOT NULL REFERENCES flow_runs(id) ON DELETE CASCADE,
|
|
step_id TEXT NOT NULL,
|
|
kind TEXT NOT NULL,
|
|
agent TEXT,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
task_id UUID REFERENCES tasks(id) ON DELETE SET NULL,
|
|
chat_id UUID REFERENCES chats(id) ON DELETE SET NULL,
|
|
input TEXT,
|
|
output TEXT,
|
|
error TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
|
CONSTRAINT flow_steps_kind_chk CHECK (kind IN ('agent', 'code')),
|
|
CONSTRAINT flow_steps_status_chk CHECK (status IN ('pending', 'running', 'completed', 'failed', 'skipped', 'cancelled')),
|
|
UNIQUE (run_id, step_id)
|
|
);
|
|
|
|
-- Runs history query (GET /api/runs?project_id=).
|
|
CREATE INDEX IF NOT EXISTS flow_runs_project_created_idx ON flow_runs(project_id, created_at DESC);
|
|
|
|
-- Ready-wave derivation + resume scans (flow_steps WHERE run_id = ? AND status = ?).
|
|
CREATE INDEX IF NOT EXISTS flow_steps_run_status_idx ON flow_steps(run_id, status);
|
|
|
|
-- onTaskTerminal callback: look up the step owning a completed task.
|
|
CREATE INDEX IF NOT EXISTS flow_steps_task_id_idx ON flow_steps(task_id);
|
|
|
|
-- v2.8.0 (Phase 4): widen the status CHECKs to include 'cancelled' so the run
|
|
-- pane's stop button has a terminal state to record (the plan Notes flagged this
|
|
-- gap). Phase 2 applied these tables with the narrower set, so the inline CHECK
|
|
-- edits above are no-ops on the existing DB (CREATE TABLE IF NOT EXISTS skips an
|
|
-- existing table) — widen via the repo's DROP-IF-EXISTS → guarded-ADD discipline.
|
|
-- Pure ADD of a new allowed value, so no row UPDATE is needed (no value renamed).
|
|
ALTER TABLE flow_runs DROP CONSTRAINT IF EXISTS flow_runs_status_chk;
|
|
DO $$ BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'flow_runs_status_chk') THEN
|
|
ALTER TABLE flow_runs ADD CONSTRAINT flow_runs_status_chk
|
|
CHECK (status IN ('running', 'completed', 'failed', 'cancelled'));
|
|
END IF;
|
|
END $$;
|
|
|
|
ALTER TABLE flow_steps DROP CONSTRAINT IF EXISTS flow_steps_status_chk;
|
|
DO $$ BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'flow_steps_status_chk') THEN
|
|
ALTER TABLE flow_steps ADD CONSTRAINT flow_steps_status_chk
|
|
CHECK (status IN ('pending', 'running', 'completed', 'failed', 'skipped', 'cancelled'));
|
|
END IF;
|
|
END $$;
|