-- Poimen Workflows schema -- Tables: workflows, workflow_executions, execution_logs, activity_traces, workflow_stats, workflow_memory_links -- Integrates with temporal workflow orchestrator and memory service -- Workflows (canvas definitions with JSONB nodes/edges) CREATE TABLE IF NOT EXISTS workflows ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), customer_id TEXT NOT NULL, name TEXT NOT NULL, description TEXT, status TEXT NOT NULL CHECK (status IN ('draft', 'active', 'archived')) DEFAULT 'draft', version INT NOT NULL DEFAULT 1, -- Canvas data (React Flow format) nodes JSONB NOT NULL DEFAULT '[]'::jsonb, -- WorkflowNode[] edges JSONB NOT NULL DEFAULT '[]'::jsonb, -- WorkflowEdge[] -- Metadata created_by TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), last_executed_at TIMESTAMPTZ, CONSTRAINT workflow_name_per_customer UNIQUE (customer_id, name) ); CREATE INDEX idx_workflows_customer ON workflows(customer_id); CREATE INDEX idx_workflows_status ON workflows(status); CREATE INDEX idx_workflows_created_at ON workflows(created_at DESC); -- Workflow executions (runs triggered by user) CREATE TABLE IF NOT EXISTS workflow_executions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), workflow_id UUID NOT NULL REFERENCES workflows(id) ON DELETE CASCADE, customer_id TEXT NOT NULL, -- Temporal details temporal_id TEXT NOT NULL UNIQUE, -- Temporal workflow execution ID status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'success', 'failed', 'cancelled')) DEFAULT 'pending', -- Input/Output inputs JSONB NOT NULL, outputs JSONB, -- Timing started_at TIMESTAMPTZ NOT NULL DEFAULT now(), completed_at TIMESTAMPTZ, duration_ms INT, -- Error tracking error_message TEXT, error_count INT DEFAULT 0, CONSTRAINT duration_when_completed CHECK ( (status IN ('success', 'failed') AND completed_at IS NOT NULL) OR (status IN ('pending', 'running', 'cancelled')) ) ); CREATE INDEX idx_executions_workflow ON workflow_executions(workflow_id); CREATE INDEX idx_executions_customer ON workflow_executions(customer_id); CREATE INDEX idx_executions_status ON workflow_executions(status); CREATE INDEX idx_executions_temporal_id ON workflow_executions(temporal_id); CREATE INDEX idx_executions_started_at ON workflow_executions(started_at DESC); -- Execution logs (detailed activity logs) CREATE TABLE IF NOT EXISTS execution_logs ( id BIGSERIAL PRIMARY KEY, execution_id UUID NOT NULL REFERENCES workflow_executions(id) ON DELETE CASCADE, -- Node/Activity info node_id TEXT NOT NULL, -- "activity-123" from canvas activity_name TEXT NOT NULL, -- "CloneRepo", "AnalyzeCode", etc. -- Log entry level TEXT NOT NULL CHECK (level IN ('info', 'warn', 'error', 'debug')), message TEXT NOT NULL, metadata JSONB, -- Arbitrary structured data (duration, result, etc.) -- Timing logged_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT log_order UNIQUE (execution_id, logged_at, id) ); CREATE INDEX idx_logs_execution ON execution_logs(execution_id); CREATE INDEX idx_logs_node ON execution_logs(execution_id, node_id); CREATE INDEX idx_logs_level ON execution_logs(level); CREATE INDEX idx_logs_logged_at ON execution_logs(logged_at DESC); -- Activity execution trace (detailed per-activity metrics) CREATE TABLE IF NOT EXISTS activity_traces ( id BIGSERIAL PRIMARY KEY, execution_id UUID NOT NULL REFERENCES workflow_executions(id) ON DELETE CASCADE, node_id TEXT NOT NULL, -- Activity details activity_name TEXT NOT NULL, parameters JSONB NOT NULL, result JSONB, -- Timing started_at TIMESTAMPTZ NOT NULL, completed_at TIMESTAMPTZ, duration_ms INT, -- Retry info attempt INT DEFAULT 1, retry_reason TEXT, -- Status status TEXT NOT NULL CHECK (status IN ('running', 'success', 'failed', 'skipped')), error_message TEXT ); CREATE INDEX idx_traces_execution ON activity_traces(execution_id); CREATE INDEX idx_traces_activity ON activity_traces(activity_name); CREATE INDEX idx_traces_status ON activity_traces(status); CREATE INDEX idx_traces_started_at ON activity_traces(started_at DESC); -- Workflow stats (materialized for fast dashboard queries) CREATE TABLE IF NOT EXISTS workflow_stats ( workflow_id UUID PRIMARY KEY REFERENCES workflows(id) ON DELETE CASCADE, customer_id TEXT NOT NULL, total_runs INT DEFAULT 0, successful_runs INT DEFAULT 0, failed_runs INT DEFAULT 0, avg_duration_ms NUMERIC, min_duration_ms INT, max_duration_ms INT, last_30d_runs INT DEFAULT 0, last_30d_success_rate NUMERIC, updated_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX idx_stats_customer ON workflow_stats(customer_id); -- Memory links (connect executions to memory/lessons learned) CREATE TABLE IF NOT EXISTS workflow_memory_links ( execution_id UUID NOT NULL REFERENCES workflow_executions(id) ON DELETE CASCADE, memory_node_sha TEXT NOT NULL REFERENCES memory_node(sha256) ON DELETE CASCADE, relationship TEXT NOT NULL CHECK (relationship IN ('generated', 'used', 'learned', 'failed_on')), -- Context created_at TIMESTAMPTZ NOT NULL DEFAULT now(), notes TEXT, PRIMARY KEY (execution_id, memory_node_sha, relationship) ); CREATE INDEX idx_memory_links_memory_node ON workflow_memory_links(memory_node_sha); CREATE INDEX idx_memory_links_execution ON workflow_memory_links(execution_id); -- View: Recent executions with workflow context CREATE OR REPLACE VIEW v_recent_executions AS SELECT we.id, we.workflow_id, w.name as workflow_name, we.customer_id, we.status, we.started_at, we.completed_at, we.duration_ms, we.error_message, (SELECT COUNT(*) FROM execution_logs WHERE execution_id = we.id) as log_count, (SELECT COUNT(*) FROM activity_traces WHERE execution_id = we.id) as activity_count FROM workflow_executions we JOIN workflows w ON we.workflow_id = w.id ORDER BY we.started_at DESC; -- View: Execution timeline (for state machine visualization) CREATE OR REPLACE VIEW v_execution_timeline AS SELECT el.execution_id, el.logged_at, el.node_id, el.activity_name, el.level, el.message, at.duration_ms as activity_duration, at.status as activity_status FROM execution_logs el LEFT JOIN activity_traces at ON el.execution_id = at.execution_id AND el.node_id = at.node_id ORDER BY el.execution_id, el.logged_at;