feat(trpc): validate trigger configs and expose trigger CRUD
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
createrejectskind: "schedule"— an automation's schedule is edited viaautomation.update, which keeps the trigger in sync.updaterejects a config change on a schedule trigger, and rejects changing any trigger's kind.deleterejects 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 configs | pass |
scope null, {mode:"any"}, {mode:"list"} | pass |
a label literally named "any" inside a list | pass |
bare string[] scope (the shape we rejected) | rejected |
| empty scope list, empty events | rejected |
includeForks: true | rejected |
actor anyone / org_members / id list | pass |
bogus actor, unknown kind, bad dtstart | rejected |
create derives kind from config, enabled defaults true | pass |
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).createderiveskindfromconfig.kind; computenextRunAtonly for schedule configs. - Keep schedule triggers read‑only in this router: reject
createwithkind: "schedule", reject config updates on schedules, and reject deleting schedules. - Prevent kind changes on
update.listoptionally filters byautomationIdand orders by creation time. - Enforce safe shapes: tagged three‑state
scope(null/{mode:"any"}/{mode:"list"}), strictactorunion, andincludeForksfixed tofalse.
Review notes
- No migration; the
config.kind = kindCHECK 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.