-- Worker system: runners, job definitions, jobs, and job log. -- Migration: 015_jobs_runners -- Date: 2026-02 BEGIN; -------------------------------------------------------------------------------- -- Runners (registered compute workers) -------------------------------------------------------------------------------- CREATE TABLE runners ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT UNIQUE NOT NULL, token_hash TEXT NOT NULL, token_prefix TEXT NOT NULL, tags TEXT[] NOT NULL DEFAULT '{}', status TEXT NOT NULL DEFAULT 'offline', last_heartbeat TIMESTAMPTZ, last_job_id UUID, metadata JSONB DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_runners_status ON runners(status); CREATE INDEX idx_runners_token ON runners(token_hash); -------------------------------------------------------------------------------- -- Job Definitions (parsed from YAML, stored for reference and FK) -------------------------------------------------------------------------------- CREATE TABLE job_definitions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT UNIQUE NOT NULL, version INTEGER NOT NULL DEFAULT 1, trigger_type TEXT NOT NULL, scope_type TEXT NOT NULL, compute_type TEXT NOT NULL, runner_tags TEXT[] NOT NULL DEFAULT '{}', timeout_seconds INTEGER NOT NULL DEFAULT 600, max_retries INTEGER NOT NULL DEFAULT 1, priority INTEGER NOT NULL DEFAULT 100, definition JSONB NOT NULL, enabled BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_job_defs_trigger ON job_definitions(trigger_type); CREATE INDEX idx_job_defs_enabled ON job_definitions(enabled) WHERE enabled = true; -------------------------------------------------------------------------------- -- Jobs (individual compute job instances) -------------------------------------------------------------------------------- CREATE TYPE job_status AS ENUM ( 'pending', 'claimed', 'running', 'completed', 'failed', 'cancelled' ); CREATE TABLE jobs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), job_definition_id UUID REFERENCES job_definitions(id) ON DELETE SET NULL, definition_name TEXT NOT NULL, status job_status NOT NULL DEFAULT 'pending', priority INTEGER NOT NULL DEFAULT 100, item_id UUID REFERENCES items(id) ON DELETE CASCADE, project_id UUID REFERENCES projects(id) ON DELETE SET NULL, scope_metadata JSONB DEFAULT '{}', runner_id UUID REFERENCES runners(id) ON DELETE SET NULL, runner_tags TEXT[] NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), claimed_at TIMESTAMPTZ, started_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, timeout_seconds INTEGER NOT NULL DEFAULT 600, expires_at TIMESTAMPTZ, progress INTEGER DEFAULT 0, progress_message TEXT, result JSONB, error_message TEXT, retry_count INTEGER NOT NULL DEFAULT 0, max_retries INTEGER NOT NULL DEFAULT 1, created_by TEXT, cancelled_by TEXT ); CREATE INDEX idx_jobs_status ON jobs(status); CREATE INDEX idx_jobs_pending ON jobs(status, priority, created_at) WHERE status = 'pending'; CREATE INDEX idx_jobs_item ON jobs(item_id); CREATE INDEX idx_jobs_runner ON jobs(runner_id); CREATE INDEX idx_jobs_definition ON jobs(job_definition_id); -------------------------------------------------------------------------------- -- Job Log (append-only progress entries) -------------------------------------------------------------------------------- CREATE TABLE job_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), job_id UUID NOT NULL REFERENCES jobs(id) ON DELETE CASCADE, timestamp TIMESTAMPTZ NOT NULL DEFAULT now(), level TEXT NOT NULL DEFAULT 'info', message TEXT NOT NULL, metadata JSONB DEFAULT '{}' ); CREATE INDEX idx_job_log_job ON job_log(job_id, timestamp); COMMIT;