"""Seed the provisioning.dispatch scheduled job row. Revision ID: 041 Revises: 040 Create Date: 2026-04-21 Adds a ``scheduled_jobs`` row so the worker picks up the new ``provisioning.dispatch`` handler (see ``app/scheduled_jobs/handlers.py``). The handler walks ``provisioning_tasks`` and dispatches pending ones to their drivers. Runs every 30 seconds by default — frequent enough that an approval → grant feels snappy (typical sub-minute provisioning) but cheap (each tick is O(pending tasks) which is usually 0-1). Idempotent: re-running this migration on a DB that already has the row is a no-op. """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "041" down_revision = "040" branch_labels = None depends_on = None _JOB_NAME = "provisioning.dispatch" def upgrade() -> None: bind = op.get_bind() exists = bind.execute( sa.text( "SELECT 1 FROM scheduled_jobs WHERE name = :n LIMIT 1", ), {"n": _JOB_NAME}, ).scalar() if exists: return bind.execute(sa.text( """ INSERT INTO scheduled_jobs (id, name, display_name, description, handler_key, trigger_type, interval_seconds, cron_expr, timezone, enabled, config, run_count, failure_count, created_at, updated_at) VALUES (UUID(), :n, :dn, :desc, :hk, 'interval', :ivl, NULL, 'UTC', 1, JSON_OBJECT('limit', 50), 0, 0, NOW(), NOW()) """, ), { "n": _JOB_NAME, "dn": "Provisioning 任务派发", "desc": ( "每 30 秒扫 provisioning_tasks。status=pending 的任务分发到" "对应驱动(Zoom / Slack / GitHub / Feishu / Google Workspace)。" "这是审批通过 → 自动开通、离职 auto_revoke → 自动回收的执行引擎。" ), "hk": _JOB_NAME, "ivl": 30, }) def downgrade() -> None: op.get_bind().execute( sa.text("DELETE FROM scheduled_jobs WHERE name = :n"), {"n": _JOB_NAME}, )