feat(trpc): validate trigger configs and expose trigger CRUD

Draft
SA
20d ago
+286-0

The trigger tables have existed since the cutover with nothing able to write a non-schedule row. This is the unblocker: config validation plus automation.triggers.{list,create,update,delete}.

No migration.

Validation

One zod schema per trigger kind, combined as a discriminated union on kind, mirroring the TS types in packages/db/src/schema/types.ts.

create derives the kind column from config.kind rather than accepting it. The database has a CHECK (config->>'kind' = kind::text), and deriving means a caller can never be the one to violate it — the two can't disagree because only one is an input.

Scope stays the tagged three-state union (null / {mode:"any"} / {mode:"list", ids}). Verified the reason it exists: a GitHub label literally named "any" parses correctly inside a list, which a bare string[] | "any" encoding could not express without reserving a magic value in a namespace users control.

includeForks is typed as the literal false. Fork payloads carry attacker-controlled content into a checkout an agent then runs in, so enabling it should be a deliberate schema change with a threat model attached, not a boolean someone can pass.

Schedule triggers are read-only through this router

  • create rejects kind: "schedule" — an automation's schedule is edited via automation.update, which keeps the trigger in sync.
  • update rejects a config change on a schedule trigger, and rejects changing any trigger's kind.
  • delete rejects schedule triggers outright: the dispatcher inner-joins the schedule trigger, so removing one wouldn't just stop the automation firing, it would make it disappear from every listing.

That last one is the failure mode worth calling out — it's silent, and it looks like data loss from the UI.

Verified

17 validation cases run against the real schemas:

github minimal / webhook / linear / schedule configspass
scope null, {mode:"any"}, {mode:"list"}pass
a label literally named "any" inside a listpass
bare string[] scope (the shape we rejected)rejected
empty scope list, empty eventsrejected
includeForks: truerejected
actor anyone / org_members / id listpass
bogus actor, unknown kind, bad dtstartrejected
create derives kind from config, enabled defaults truepass

Ownership resolves through the automation via getAutomationForUser, since triggers have no owner of their own; org scoping is checked on the trigger row too, so a cross-org id 404s rather than reaching the composite FK and surfacing as a 500.

Typecheck, lint and the full suite pass.

Next

Webhook ingestion — the acceptance/processing split, adapter registry, and key generation — is what makes these fire. Then the GitHub connector.


Summary by cubic

Adds trigger config validation and exposes automation.triggers.{list,create,update,delete} so non‑schedule triggers can be managed. Previously, only schedule rows existed and no endpoint could create or modify other kinds.

  • Validate trigger configs with per‑kind zod schemas (discriminated by kind). create derives kind from config.kind; compute nextRunAt only for schedule configs.
  • Keep schedule triggers read‑only in this router: reject create with kind: "schedule", reject config updates on schedules, and reject deleting schedules.
  • Prevent kind changes on update. list optionally filters by automationId and orders by creation time.
  • Enforce safe shapes: tagged three‑state scope (null / {mode:"any"} / {mode:"list"}), strict actor union, and includeForks fixed to false.

Review notes

  • No migration; the config.kind = kind CHECK already exists.
  • Access control: require active org membership; resolve ownership via the automation; cross‑org ids return NOT_FOUND.

Summary by CodeRabbit

New Features

  • Added automation trigger management with support for listing, creating, updating, and deleting triggers.
  • Added trigger configurations for schedules, webhooks, GitHub, Slack, Linear, and Sentry.
  • Added validation for trigger-specific settings, filters, actor restrictions, event limits, and disabled forks.
  • Schedule triggers now calculate their next run time automatically.
  • Added access controls to ensure only authorized automation owners can manage triggers.