-- M8 P3: PostgreSQL-backed scheduled task definitions and durable execution queue. CREATE TABLE IF NOT EXISTS gateway.scheduled_tasks ( id uuid PRIMARY KEY, code text NOT NULL UNIQUE CHECK (code ~ '^[a-z][a-z0-9_-]{1,63}$'), name text NOT NULL CHECK (length(name) BETWEEN 1 AND 128), description text NOT NULL DEFAULT '' CHECK (length(description) <= 4000), cron_expression text NOT NULL CHECK (length(cron_expression) <= 128), timezone text NOT NULL DEFAULT 'UTC' CHECK (length(timezone) <= 128), target_type text NOT NULL CHECK (target_type IN ('application', 'digital_employee')), target_code text NOT NULL CHECK (length(target_code) BETWEEN 1 AND 64), prompt text NOT NULL CHECK (length(prompt) BETWEEN 1 AND 100000), variables jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(variables) = 'object'), skill_ids uuid[] NOT NULL DEFAULT '{}', mcp_server_ids uuid[] NOT NULL DEFAULT '{}', conversation_id text NOT NULL DEFAULT '' CHECK (length(conversation_id) <= 128), notification_channel_id uuid REFERENCES gateway.notification_channels(id) ON DELETE SET NULL, encrypted_api_key bytea NOT NULL, api_key_kek_version integer NOT NULL CHECK (api_key_kek_version > 0), enabled boolean NOT NULL DEFAULT false, next_run_at timestamptz, last_run_at timestamptz, last_status text NOT NULL DEFAULT '' CHECK (last_status IN ('', 'success', 'failed')), last_error text NOT NULL DEFAULT '' CHECK (length(last_error) <= 4000), created_by uuid NOT NULL REFERENCES gateway.admin_accounts(id), revision bigint NOT NULL DEFAULT 1, created_at timestamptz NOT NULL DEFAULT clock_timestamp(), updated_at timestamptz NOT NULL DEFAULT clock_timestamp(), CHECK ((enabled AND next_run_at IS NOT NULL) OR (NOT enabled)) ); CREATE INDEX IF NOT EXISTS scheduled_tasks_due_idx ON gateway.scheduled_tasks (next_run_at, id) WHERE enabled; CREATE TABLE IF NOT EXISTS gateway.scheduled_task_runs ( id uuid PRIMARY KEY, task_id uuid NOT NULL REFERENCES gateway.scheduled_tasks(id) ON DELETE CASCADE, trigger_type text NOT NULL CHECK (trigger_type IN ('scheduled', 'manual')), scheduled_for timestamptz NOT NULL, status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'success', 'failed')), attempts integer NOT NULL DEFAULT 0 CHECK (attempts >= 0), worker_id text NOT NULL DEFAULT '' CHECK (length(worker_id) <= 128), started_at timestamptz, finished_at timestamptz, response jsonb, error text NOT NULL DEFAULT '' CHECK (length(error) <= 4000), created_at timestamptz NOT NULL DEFAULT clock_timestamp(), UNIQUE (task_id, trigger_type, scheduled_for) ); CREATE INDEX IF NOT EXISTS scheduled_task_runs_pending_idx ON gateway.scheduled_task_runs (created_at, id) WHERE status = 'pending'; CREATE INDEX IF NOT EXISTS scheduled_task_runs_history_idx ON gateway.scheduled_task_runs (task_id, created_at DESC); CREATE INDEX IF NOT EXISTS scheduled_task_runs_running_idx ON gateway.scheduled_task_runs (started_at) WHERE status = 'running';