cmux-next backend: automations (SchedulerDO + Workflows) and integrations (GitHub App, Linear, Slack)

Merged
LA
1mo ago
+14049-1190

Automations and integrations for the cmux-next backend (spec manaflow-ai/cmux-next-spec: cloud-and-automations.md, integrations.md; decision D13).

Automations. One SchedulerDO per team owns definitions, schedules and recent runs on the shared OwnerDO. Triggers: cron (5 fields, IANA zone), manual, continue (next run after a cooldown until goal met or max runs), signed webhook, and integration events; message and presence triggers are stored and shown as not yet supported. Each run is one Cloudflare Workflow instance whose id is the run id. Fires are system ops keyed fire:<automation>:<trigger>:<scheduled_at>, so a repeated alarm replays. A watchdog marks runs dead when their Workflow ended without a final report and enforces the wall-clock budget. Ops: automation.create|update|delete|run|list|get|runs.list|webhook.get.

Integrations. ConnectionDO per team (the spec says per connection; see COORDINATION) with GitHub App, Linear and Slack. Provider tokens are sealed (AES-GCM envelope, INTEGRATIONS_KEK) outside state, events, the ledger and the projection. OAuth state is an ES256 JWT bound to user and team; the provider redirects to the dashboard, which finishes with the user's own session. Provider ops github.issue.comment, linear.issue.create, slack.post_as_bot run in an external-effect ledger (replay, mutation.indeterminate). Webhooks POST /v1/hooks/{github,slack,linear} verify, then route through AccountIndexDO to event triggers.

Shared changes (agreed with the backend lead). OwnerDO nextWakeAt/onWake share the DO alarm with the outbox drain; submitSystem with principal kind system (built only in a DO, internal ops only); http.ts routes by an owner table and reads honor principal kinds. Migrations 0003 (automations, automation_runs) and 0004 (connections), expand only. Dashboard pages /automations, /integrations, /integrations/callback.

Verified. 45 workerd tests (reducers, alarms, Workflows, webhooks, provider clients on fake HTTP, Slack and Linear end to end, revocation, grants). Migrations and drains on scratch Postgres 17. A live preview Worker on Cloudflare (deleted after): manual, webhook and every-minute cron runs succeeded through real Workflows; a duplicate instance id is refused with instance.already_exists.

Not verified. Real GitHub, Linear and Slack apps (none exist yet). Whether the CI Cloudflare token can create Workflows (this PR's preview job shows it). Staging deploy.

Changelog

none

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Adds automations (SchedulerDO + Workflows) and integrations (GitHub App, Linear, Slack) to the cmux-next backend, per spec cloud-and-automations.md, integrations.md, and decision D13.

Automations. One SchedulerDO per team owns definitions, schedules, and recent runs on the shared OwnerDO. Triggers: cron (five fields, IANA zone), manual, continue, signed webhook, and integration events; message and presence triggers are stored and shown as not yet supported. Each run is one Cloudflare Workflow instance whose id is the run id. Fires are system ops keyed fire:<automation>:<trigger>:<scheduled_at>, so a repeated alarm replays. Dispatch sets a wall-clock deadline in the dispatch and report ops; the alarm wakes once at it for runs still open, terminating a running Workflow or marking one that ended without its final report dead. Retry backoff for fires, dispatches, and deadline checks persists in a DO table; webhook dedupe uses 30-day delivery keys in the SchedulerDO's seen_deliveries table (GitHub signs no timestamp). Webhook inputs of finished or pruned runs leave with the replay window; queued ones are kept. Ops: automation.create|update|delete|run|list|get|runs.list|webhook.get.

Integrations and shared engine. ConnectionDO manages connections per team; provider tokens are sealed (AES-GCM envelope, INTEGRATIONS_KEK) outside state. OAuth state is a short-lived ES256 JWT bound to user and team; the provider redirects to the dashboard, which finishes with the user's own session. Provider ops github.issue.comment, linear.issue.create, slack.post_as_bot run in an external-effect ledger (replay, mutation.indeterminate). A typed team policy gates GitHub: by default a connection reaches only the linking user's repositories; teams can widen to the whole installation, require an org admin, or allowlist repos, and an SSO/MDM hook applies a locked policy. Webhooks POST /v1/hooks/{github,slack,linear} verify, then route through AccountIndexDO to event triggers. On the shared engine, OwnerDO nextWakeAt/onWake share the DO alarm with the outbox drain and prune the request ledger (7-day retention) in batches; clients drop intents older than 24 hours instead of resending; submitSystem allows system principals built only inside a DO. Migrations 0003 (automations) and 0004 (connections) are expand-only. Dashboard pages /automations, /integrations, /integrations/callback.

Verified with workerd tests (reducers, alarms, Workflows, webhooks, provider clients on fake HTTP, Slack and Linear end to end, revocation, run deadlines, ledger retention), migrations on scratch Postgres 17, and a live preview Worker where manual, webhook, and every-minute cron runs succeeded through real Workflows. Not verified: real GitHub, Linear, and Slack apps (none exist yet), CI Cloudflare token creating Workflows, and staging deploy.